在kotlin种,函数是一等公民,我们就需要对kotlin的函数进行一些较深入的了解,主要是下面四块,其他一些常见的,比如顶层函数就不说了
主要是四块
- 函数引用与匿名函数
- Lambda表达式
- 函数内联
- 函数接受者
函数引用与匿名函数
函数引用
在Kotlin种一切皆是对象,函数也不例外,我们可以把一个函数
(实质上是一个对象)作为参数去传递,通过::
的方式可以把一个函数转换一个指向该函数的引用,然后该引用就可以作为参数传递。
1 | fun test(a:Int){ |
匿名函数
::
的方式是Kotlin会为我们生成一个匿名对象,调用该对象的invoke方法或者是按照方法调用该对象,即等同于调用该方法。除了通过::
的方式,我们还可以通过匿名函数来得到一个指向该方法的引用(也是生成了对象):
1 | // fun后不需要函数名称 |
在kotlin种,我们可以把匿名函数作为变量传递,所以本质上匿名函数不是一个函数,而是一个对象。通过上面匿名函数的例子,我们可以看到匿名函数进行一定的转换,他是可以变为lambda的,比如上面的
1 | val f ={a:Int-> |
从本质上说,Lambda也是一个匿名函数类型的对象,下面我们开始分析下Lambda的一些特点,在调用Lambda与匿名函数的时候,有一个区别是能否使用return
,后者可以,前者不可以,后者可以在匿名方法的声明中使用return
关键字,有返回值的时候也只能使用return
关键字,前者在非inline
函数中无法使用return
,但是可以直接return@
Lambda表达式
一般Lambda会有三种用法:
- 声明为对象被调用
- 声明为函数的参数(这样的函数也叫高阶函数)
- 作为函数参数返回(这种也是高阶函数)
声明为对象的时候
1 | // 无参的 |
作为函数的参数的时候(注意Lambda的参数个数相同是看作同样的方法,他们的方法名称不能一样,其中Lambda表达式的参数部分的()
不能省略)
1 | // 无参和无返回值的 |
作为函数返回值(闭包),因为Lambda表达式它最终来说还是一个对象。
1 |
|
Lambda表达式的返回值:在Kotlin的Lambda中,是他的最后一行作为返回值返回的,我们不需要声明返回值的类型,也不需要使用return
,他会自动推断
1 | val sum = {a:Int,b:Int->a+b} //返回值是Int |
假如我们需要终止Lambda的后续调用,可以使用return@
的形式。
1 | fun test(f: (Int) -> Boolean) { |
Lambda的别名,有时候,我们写函数参数的时候需要有挺多个或者是重复的Lambda,我们可以通过定义别名的方式来简便写法,使用typealias
关键字,这样代码看起来就简洁很多。
1 | typealias FunOne = ()->Unit |
Lambda的传参调用例子:
- 当Lambda表达式只有一个参数的时候可以省略,使用
it
指代 - 当参数不需要被调用的时候,可以使用
_
。
1 | // 当lambda变量是参数的最后一个的时候,可以写在调用的外面,当只有一个参数而且该参数是lambda的时候,可以省略() |
Lambda
与SAM转换:参考文章Kotlin的SAM转换
- SAM接口,即单一方法的Java定义的接口其实就是Kotlin种的Lambda兼容了Java中的SAM,让他的调用可以像Kotlin调用普通的高阶函数一样
- kotlin定义的接口类型作为参数,不能使用Lambda,需要使用匿名内部类的方式(kotlin在1.4之后做了一个优化,使用fun修饰的只有单一方法的interface可以)
KClass
,就是Kotlin种定义的Class对象,可以使用return
,但是需要显示的转换为KClass
,不能像SAM
那样省略,这种做法也一般不推荐。
1 | 1. |
函数内联
函数内联使用的关键字是inline
,用于修饰方法。inline
函数会在被调用的地方复制一份方法代码(代码内嵌),同时会把函数参数也给铺平(即方法体+传参的lambda都会被铺平),而不是通过方法栈的形式被调用。比如
1 | fun main(args: Array<String>) { |
那么inline
内联函数有什么作用呢?经过上面对于匿名函数与Lambda的梳理,我们知道他们从本质上来说是一类对象,那么这里就会引出一个问题,假如我们频繁的调用高阶函数,那么他每一次就会产生一个对象,假如循环数较大就会耗费较多的内存。比如
1 | fun main(args: Array<String>) { |
在循环中调用了test()
方法,就会创建大量的看不到的函数对象,这时候我们引入了inline
方法就可以解决这个问题了。
1 | fun main(args: Array<String>) { |
这样也就不会每次循环都生成一个函数对象了,inline
修饰的方法会在编译的时候把该方法复制一份到被调用处。
对于inline
函数的函数体不适宜过大,因为随意调用可能会导致inline
函数被到处复制代码从而导致编译之后的包变大,一般而言是对于循环中调用的高阶函数使用该修饰符,普通方法中是不推荐使用inline
的。当然了,对于我们一些lib库中定义的高阶方法,我们是推荐使用inline
修饰的,因为你不知道外部调用者是否会在循环体中调用该方法,所以最好是加上inline
修饰。
inline
的函数调用支持return
去终止方法的后续调用,即假如在inline
函数中使用了return
而不是return@
的形式,他会直接终止后续的调用,例如
1 | fun test(f: () -> Unit) { |
inline
与refied
关键字:首先refied
是修饰泛型参数的,用在inline
方法中。我们在Java中是无法通过泛型来获取Class
对象的,但是kotlin可以做到,即通过inline
修饰的方法加上使用refied
修饰的泛型即可拿到。
1 | public <T> void test(List<T> list){ |
需要注意的是使用了refield
修饰的泛型不会像java一样会被搽除,因为编译的时候他会把方法体复制并具体化。
noinline
,用于修饰lambda
参数(不能修饰普通参数),他是与inline
相反的,使用它修饰的lambda
参数不会被铺平,会生成函数对象,同时它只能是在inline
函数中使用,这时候是只有调用的方法体被铺平了。
1 | inline fun test(f: () -> Unit, noinline f2: () -> Unit) { |
noinline
作用在于可以做到让方法返回参数中的函数引用,在inline
修饰的方法中,我们是直接让方法体全都铺平了,包括作为参数传入的lambda,他是无法做到返回一个作为参数传入的函数对象的。例如
1 | inline fun test(f: () -> Unit): () -> Unit { |
noinlie
总来的来说用来局部的指向性的关闭inline
优化,同时注意在noinlie
的参数中也是不允许使用return
的,因为他是相当于普通的函数参数。
noinlie
的使用还有一个点,在一个inline
函数调用一个非inline
函数的时候,假如是需要把参数中的lambda参数传递给非inline
函数,则该参数需要使用noinlie
修饰,否则的话会报错,因为inline
函数的函数参数他会被铺平,但是被调用者是不会的,这时候他胡产生冲突。
1 | fun test(f:()->Unit){ |
crossinline
,它也是用于修饰inline
方法的函数参数的,与noinline
的一些作用相反,在inline
函数调用非inline
函数的时候,我依然希望传入lambda是铺平的,它也可以传递给别的非inline
,比如
1 | fun test(f: () -> Unit) { |
注意,就算我们使用了crossinline
修饰,参数也不能作为参数传递给另外一个非inline
函数,假如需要传递,还是需要noinlie
,需要函数参数作为返回值也是需要使用noinline
。例如
1 | inline fun test4(crossinline f: () -> Unit) { |
还有一点就是我们的函数参数被非inline
方法间接调用,使用了crossinline
来加强内联,则我们调用处是不能使用return
关键字的,因为不知道是结束哪一处的调用,比如
1 | fun main() { |
总结就是:
inline
修饰函数,一般只修饰高级函数,修饰之后会把方法体和函数对象铺平调用,使用了return
关键字之后会结束外部方法的调用。noinline
用于修饰inline
方法中的函数类型参数,被修饰的参数可以传递给非inline
函数,也可以作为返回值,不能使用return
。crossline
,假如我们函数参数需要被间接调用,而且我们希望该参数也内联,则使用crossline
,调用时不能使用return
。
函数接受者
我们在kotlin中经常的使用apply
,also
,let
,run
等,我们可以看一下他们的声明
1 | public inline fun <T> T.apply(block: T.() -> Unit): T { |
我们可以看到参数block
中的函数类型使用了T.()
,这就是一个函数接受者的例子,表示可以在调用的时候,直接使用T
中的变量和方法,block
也需要被T
的对象调用,所以apply
中的this
代表的是调用对象。
1 | class Person(val name:String,val age:Int){ |
T().
与(T)
的区别,比如apply
与also
,
1 | public inline fun <T> T.also(block: (T) -> Unit): T { |
调用的时候also
中的this
是当前类,调用者是通过it
来获取,这就是T().
与(T)
的区别。