Kotlin inline内联函数
今日介绍下 Kotlin
中 十分重要的 内联函数,小伙伴系紧鞋带准备发车
一般函数调用
下面测验整数相加的状况
fun calculate() { println(add(a = 1, b = 3)) } fun add(a: Int, b: Int) = a + b
反编译查看下 java
代码(Android Studio或idea下能够运用 kotlin
插件直接查看,途径是 Tools → Kotlin → Show Kotlin Bytecode → DECOMPILE
)
public final void calculate() { int var1 = this.add(1, 3); System.out.println(var1); } final int add(int a, int b) { return a + b; }
能够看到这是一个正常的函数调用,在calculate
函数内部调用了add
inline润饰的一般函数
咱们再来看下将add添加了inline
的效果
fun calculate() { println(add(a = 1, b = 3)) } inline fun add(a: Int, b: Int) = a + b
反编译查看下 java
代码
public final void calculate() { byte a$iv = 1; int b$iv = 3; int $i$f$add = false; int var1 = a$iv + b$iv; System.out.println(var1); } final int add(int a, int b) { int $i$f$add = 0; return a + b; }
能够看到calculate
调用了inline
函数add
,编译时期将add
函数办法体拷贝到了调用的当地,意味着办法的调用栈少了一层
add
函数虽然能够是能够添加inline
,不过编译器却给出了警告

大约意思就是inline
函数在此并不会提高性能,inline
更适合在函数参数为函数类型的函数中运用(高阶函数)
一般高阶函数
咱们来看下不加inline
的lambda
参数的状况
fun calculate() { add(a = 1, b = 3) { println("a + b = $it") } } fun add(a: Int, b: Int, result: (Int) -> Unit): Int { val sum = a + b result.invoke(sum) return sum }
反编译查看下 java
代码
public final void calculate() { this.add(1, 3, (Function1)null.INSTANCE); } public final int add(int a, int b, Function1 result) { int sum = a + b; result.invoke(sum); return sum; }
是不是发现很古怪,咱们的lambda
居然转换成了(Function1)null.INSTANCE
,这是个啥东西?
其实(Function1)null.INSTANCE
,是由于反编译器东西在找不到等效的 Java 类时的显现的结果。
这个时分咱们就需求运用到咱们的反编译东西 jadx
了,这儿附带 jadx 的地址,有需求学习的小伙伴可自行查阅
jadx
下的代码是这样的
public final void calculate() { add(1, 3, calculate$1.INSTANCE); } public final int add(int a, int b, Function1<? super Integer, Unit> result) { Intrinsics.checkNotNullParameter(result, "result"); int sum = a + b; result.mo2833invoke(Integer.valueOf(sum)); return sum; } final class calculate$1 extends Lambda implements Function1<Integer, Unit> { public static final calculate$1 INSTANCE = new calculate$1(); calculate$1() { super(1); } @Override // kotlin.jvm.functions.Function1 /* renamed from: invoke */ public /* bridge */ /* synthetic */ Unit mo2922invoke(Integer num) { invoke(num.intValue()); return Unit.INSTANCE; } public final void invoke(int it) { System.out.println((Object) ("a + b = " + it)); } }
能够看到 lambda
表达式转换成了一个 Function1
目标,它是 Kotlin
函数的一部分,那为什么是 Function1
呢,实际上是由于 lambda
中传递了一个参数,假设没传递参数则是 Function0
,以此类推。这个 Function1
目标的创立无疑是会耗费内存的,假设咱们的代码中存在许多的高阶函数(参数类型是函数或者回来值类型是函数),在代码编译之后那么是不是会创立许多的 Function
目标呢?这个内存的耗费是不行估计的,所以 Kotlin
官方为了优化这个点,出现了 inline
内联函数
inline润饰的高阶函数
咱们仍是继续运用上面的比如来看下 inline
内联函数是如何提高性能的
fun calculate() { add(a = 1, b = 3) { println("a + b = $it") } } inline fun add(a: Int, b: Int, result: (Int) -> Unit): Int { val sum = a + b result.invoke(sum) return sum }
继续反编译看下 java
代码
public final void calculate() { byte a$iv = 1; int b$iv = 3; int $i$f$add = false; int sum$iv = a$iv + b$iv; int var7 = false; String var8 = "a + b = " + sum$iv; System.out.println(var8); } public final int add(int a, int b, @NotNull Function1 result) { int $i$f$add = 0; Intrinsics.checkNotNullParameter(result, "result"); int sum = a + b; result.invoke(sum); return sum; }
能够看到相比于不加 inline
,办法的调用栈少了一层,而且不会生成额定的目标,这对内存还说是一个很棒的优化。一般状况下咱们在高频调用的高阶函数下运用inline
,削减内存的耗费。
noinline
noinline
刚好跟 inline
相反, 它是让 高阶函数中函数类型的参数 不参加内联,先来看下为什么会有 noinline
函数类型的参数不止能够作为函数去调用,还能够作为目标去运用,例如咱们把它作为函数回来值
咱们仍是以上面为示例修正下代码
fun calculate() { add(a = 1, b = 3, addPrev = { println("addPrev!") }, result = { println("a + b = $it") }, addPost = { println("addPost!") } ) } inline fun add(a: Int, b: Int, addPrev: () -> Unit, result: (Int) -> Unit, addPost: () -> Unit): () -> Unit { addPrev.invoke() val sum = a + b result.invoke(sum) return addPost }
上面的代码编译是不会经过的,而且编译器给出了错误提示

咱们知道在 inline
内联函数中函数类型的参数是不会创立函数目标的,它仅仅是作为一个函数体存在而不是一个函数目标,所以无法当成一个目标进行回来
假设咱们仍是需求将函数类型的参数作为目标去运用,编译器也给出了处理方案,给函数类型的参数加上 noinline
即可
fun calculate() { add(a = 1, b = 3, addPrev = { println("addPrev!") }, result = { println("a + b = $it") }, addPost = { println("addPost!") } ) } inline fun add( a: Int, b: Int, addPrev: () -> Unit, result: (Int) -> Unit, noinline addPost: () -> Unit ): () -> Unit { addPrev.invoke() val sum = a + b result.invoke(sum) return addPost }
crossinline
crossinline
是部分加强内联的意思
咱们来看下下面这个场景,在 lambda
内部直接return
fun main() { linpopopo { println("linpopopo function") return } println("main function") } inline fun linpopopo(action: () -> Unit) { action.invoke() }
这个return会完毕那个函数?linpopopo?main?
按常理来说,这个return会完毕 linpopopo函数的履行,后边的 println("main function")
会被履行,不过这儿是 linpopopo
内联函数,它在编译的时分会将函数体移到调用的当地,咱们来看下编译之后的代码就清楚了
public final void main() { int $i$f$linpopopo = false; int var3 = false; String var4 = "linpopopo function"; System.out.println(var4); } public final void linpopopo(@NotNull Function0 action) { int $i$f$linpopopo = 0; Intrinsics.checkNotNullParameter(action, "action"); action.invoke(); }
看到了吧,这儿 return 完毕的是 main函数,即最外层的函数。甚至在编译的时分会将 println("main function")
放弃掉不参加编译,由于它总是不会履行,参加编译毫无意义
那咱们在函数类型参数的 lambda
表达式中的return的结果就要看该函数是不是内联函数了,这就让咱们敲代码极端的不便当了,每个函数咱们都进去看下是否是内联函数,这对咱们的时间很大的耗费
后来 Kotlin
官方拟定了一条新规则,lambda表达式中不允许直接运用return,除非这个 Lambda 是内联函数的参数,而且完毕的是最外层的函数
处理上面的问题咱们也能够运用return@label的方法完毕代码效果域,例如直接return隐式标签 linpopopo
fun main() { linpopopo { println("linpopopo function") return@linpopopo } println("main function") } inline fun linpopopo(action: () -> Unit) { action.invoke() }
反编译再看下代码
public final void main() { int $i$f$linpopopo = false; int var3 = false; String var4 = "linpopopo function"; System.out.println(var4); String var1 = "main function"; System.out.println(var1); } public final void linpopopo(@NotNull Function0 action) { int $i$f$linpopopo = 0; Intrinsics.checkNotNullParameter(action, "action"); action.invoke(); }
干得美丽,十分契合咱们的预期,老板都夸你处理问题的方法多,加鸡腿加鸡腿
再来看下下面这个场景,有的时分咱们需求在主线程上面去履行 lambda
表达式,这儿主线程是运用协程进行切换
fun main() { linpopopo { println("linpopopo function") return } println("main function") } inline fun linpopopo(action: () -> Unit) { MainScope().launch { action.invoke() } }
这样会引起一个问题,linpopopo 函数和 main函数就属于直接调用联系,导致 lambda
表达式里的 return 无法完毕 main 函数。那么它在这儿完毕的是谁?其实压根不会完毕谁,由于上面这段代码底子不会编译经过,编译器也给出了错误提示

千呼万唤始出来,编译器让咱们运用 crossinline
去润饰函数类型的参数,这样直接调用联系才会建立
inline fun linpopopo(crossinline action: () -> Unit) { MainScope().launch { action.invoke() } }
咱们又回到了原始的问题,加了 crossinline
这儿的 lambda
表达式里的 return究竟完毕了谁?是 main 函数 仍是协程 launch效果域呢?
关于这种歧义的问题,Kotlin
官方又增加了一条新的规定,内联函数中被 crossinline
润饰的函数类型的参数不允许return
所以上面的 return
也不会编译成功,当然这儿仍是能够运用return@label的方法完毕代码效果域的
fun main() { linpopopo { println("linpopopo function") return@linpopopo } println("main function") } inline fun linpopopo(crossinline action: () -> Unit) { MainScope().launch { action.invoke() } }
总结
-
inline
: 编译时将函数体拷贝到调用的当地,削减函数类型目标的创立 -
noinline
: 部分关掉内联,处理不能把函数类型的参数作为目标来运用的问题 -
crossinline
: 部分加强内联,让内联函数中函数类型的参数能够直接被调用,而且crossinline
润饰的函数类型的参数不允许return
称谢
文中部分观点参阅了扔物线大佬的著作,小伙伴可自行查阅哦,扔物线大佬文章很生动哈哈哈哈
原文地址:Kotlin源码里成吨的noinline和crossinline是干嘛的?看完视频你回头也写了一吨