前言
前面我们说了inline关键字,不多说它很好用,
# Kotlin的inline、noinline、crossinline全面分析1
它还有2个好兄弟noinline和crossinline,我们继续来分析。
正文
noinline作为inline的反义,字面意思就是不内联,那和普通函数不就一样了? 当然不是,inline是用来修饰函数的,而这个noinline只能修饰函数的参数,也就是函数是内联的,但是这个函数类型参数不是内联的。
既然知道了这个定义,我们来看看它有什么用。
noinline
话不多说,直接看个例子:
//函数是内联的,但是参数action不是内联的
inline fun lambdaFun(noinline action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
action()
Log.i("zyh", "testLambdaFun: 调用后")
}
然后我们调用:
//调用
fun testHello(){
lambdaFun {
Log.i("zyh", "testLambdaFun: 调用中")
}
}
直接反编译:
//反编译代码
public final class TestFun {
public final void testHello() {
//创建了匿名内部类实例
Function0 action$iv = (Function0)null.INSTANCE;
int $i$f$lambdaFun = false;
Log.i("zyh", "testLambdaFun: 调用前");
action$iv.invoke();
Log.i("zyh", "testLambdaFun: 调用后");
}
}
这里我们清晰的看出lambdaFun内部的代码进行了复制铺平到调用地方,但是对于action却没有复制铺平,原因也非常简单,因为它是noinline修饰的,那它有什么用呢 我们接着分析。
使用noinline的原因
我们前面说了inline会让函数类型参数进行复制和铺平,那这个参数也就不再是函数类型参数了,毕竟它变成了几行代码,所以这就是局限性,当我们还要把它作为函数类型参数或者返回时,就要使用noinline了。
还是直接看例子:
//定义高阶函数,非内联
fun lambdaFun1(action: () -> Unit){
action()
}
然后我们定义一个内联函数:
//内联函数
inline fun lambdaFun(action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
action()
//调用高阶函数
lambdaFun1(action)
Log.i("zyh", "testLambdaFun: 调用后")
}
这种使用很常见,但是我们会发现这段代码无法编译:
原因也非常简单,想把action传递给lambdaFun1,那这个action必须函数类型参数,但是在被inline修饰的函数,其参数也会被铺平,也就不会再是函数类型了,所以这里的action要使用noinline来修饰:
//使用noinline修饰参数
inline fun lambdaFun(noinline action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
action()
lambdaFun1(action)
Log.i("zyh", "testLambdaFun: 调用后")
}
高阶函数除了把函数类型参数当做其他高阶函数的参数外,还可以作为返回值,同样这时也不能把函数类型参数给铺平为lambda表达式,我们看例子:
这里我想返回这个action,遗憾的是,和前面一样,这个action会被铺平,将不再是函数类型参数了,所以必须把action用noinline修饰:
//因为返回值要使用action,要保留action为函数类型
inline fun lambdaFun(noinline action: (() -> Unit)):() -> Unit{
Log.i("zyh", "testLambdaFun: 调用前")
action()
Log.i("zyh", "testLambdaFun: 调用后")
return action
}
上面的代码进行反编译,也能想象出,肯定函数内部会被内联,action会被编译成匿名内部类:
public final void testHello() {
Function0 action$iv = (Function0)null.INSTANCE;
int $i$f$lambdaFun = false;
Log.i("zyh", "testLambdaFun: 调用前");
//这里action不会被内联
action$iv.invoke();
Log.i("zyh", "testLambdaFun: 调用后");
}
return难题
inline和noinline说完,我们来说一个return问题,为啥return会有不一样呢 我们来慢慢细说。
非内联高阶函数
先定义一个高阶函数:
fun lambdaFun(action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
action()
Log.i("zyh", "testLambdaFun: 调用后")
}
然后进行调用,在lambda中想使用return语句:
这里直接使用return语句无法使用,提醒使用return@lambdaFun,这当然可以理解,那我只想使用return呢 有没有办法,当然可以,把lambdaFun定义为内联函数。
内联函数
改成内联函数:
inline fun lambdaFun(action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
action()
Log.i("zyh", "testLambdaFun: 调用后")
}
然后再进行调用return语句:
这里居然不报错了,由于内联函数会把函数体和lambda给复制铺平到调用地方,所以这里的return必然是返回testHello函数了,而不是lambdaFun函数。
return和inline之间的约定
所以这里为了解决lambda表达式中的return语句问题,Kotlin直接规定,在非inline函数中,return无法使用(必须return@xxx指明返回的函数),只有在inline函数中可以使用return语句,这样就不会有异议,根据inline的特性,这个return必然是返回调用者函数。
crossinline
既然我们了解了return问题,以及它return和inline之间的约定,也就是为了不产生歧义,那我们继续看问题,引出crossinline这个修饰词了。
直接看例子,再定义一个高阶函数:
//新的函数,参数是函数类型
fun lambdaFun1(postAction: () -> Unit){
postAction()
}
然后在前面定义的函数中,调用这个函数:
//调用lambdaFun1,并且使用lambda
inline fun lambdaFun(action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
lambdaFun1 {
action()
}
Log.i("zyh", "testLambdaFun: 调用后")
}
千万注意,这里和前面说noinline的例子是不一样的,这里是使用lambda表达式,在表达式内调用action()也就是其invoke函数,而不是把action这个函数类型参数进行传递,和下面是不一样的:
inline fun lambdaFun(action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
//这里期望把action还是当做类型参数,而不是铺平
lambdaFun1(action)
Log.i("zyh", "testLambdaFun: 调用后")
}
上面要区分开,现在我们是讨论第一种情况,这个代码能编译吗,我们看一下:
注意这里的报错信息:无法内联action参数,原因是它可能包含return语句。
哦?为什么呢?原因非常简单,这里的lambdaFun是内联函数,但是lambdaFun1是非内联的,根据前面return和内联函数之间的约定,只有内联函数可以使用return,否则不行,那这就尴尬了,不可能我每个内联函数都必须调用内联函数吧,这显然不符合逻辑。
突破限制,加强inline功能
因为上面的代码,永远不知道action会不会包含return语句,所以inline受限严重,这里只有突破这个限制才可以,这里采用的方式是把action加个crossinline修饰符。
然后会发现:
这里居然不报错了,看似问题解决了,它内部又做了什么优化吗 这种又内联又不内联的,怕是要把编译器整疯了,当然不是,它只是解决提出问题的人。
crossinline的作用
crossinline的作用仅仅是当有被这个修饰的参数会告诉IDE来检查你写的代码中有没有包含return,假如有的话会编译不过,就是这么简单暴力。
直接看例子对比一下,首先这里不加crossinline:
inline fun lambdaFun(action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
lambdaFun1 {
action()
}
Log.i("zyh", "testLambdaFun: 调用后")
}
调用的时候:
可以正常写return语句,当我们对这个参数加了crossinlie时:
inline fun lambdaFun(crossinline action: (() -> Unit)){
Log.i("zyh", "testLambdaFun: 调用前")
lambdaFun1 {
action()
}
Log.i("zyh", "testLambdaFun: 调用后")
}
再进行调用:
直接就不让写了,哈哈,真的简单暴力。
小结一下
其实前面我们已经说完了crossinline出现的原因以及解决的问题,以及最直接暴力解决问题的办法,但是还是总结一下,这里什么情况会出现这种问题。
也就是当在内联函数中,想把参数的执行放入其他函数中,这时这个其他函数和最外层调用这个内联函数的函数就分割开了,这时你就要注意了,这个参数是不是要用crossinline修饰了。
当然这个我们不必刻意,当你代码逻辑有问题时,这个修饰符IDE会自动提醒。
总结
最后还是总结一下子,这3个关键字涉及的东西还真不少。
-
为了解决每次调用高阶函数时给它传递lambda时都会创建匿名内部类的问题引入了inline。
-
inline修饰的函数,不仅会把函数体的内容进行复制铺平,还会把函数类型参数的内容复制铺平。
-
当在内联函数中想把某个函数类型的参数进行传递或者返回,这时就不能把这个参数给铺平,所以使用noinline修饰这个参数。
-
在lambda中使用return语句会造成歧义,不知道返回哪一层,所以Kotlin直接定义非inline函数时不能使用return,当是inline函数时,return是返回调用着那一层函数。
-
在内联函数中,调用非内联的高阶函数,把lambda传递到非内联函数中,将和上面造成冲突,无法判断这个参数的lambda中有没有return,为了解决这个问题,引入了crossinline。
-
crossinline的目的是告诉IDE,这个参数的lambda不能写return语句。