Kotlin 针对函数提供了几个关键字 inline noinline crossinline,其涉及 Kotlin 中内联函数和 lambda
相关的问题。
概览
inline
: 声明在编译时,将函数的代码拷贝到调用的地方(内联)oninline
: 声明inline
函数的形参中,不希望内联的lambda
crossinline
: 表明inline
函数的形参中的lambda
不能有return
inline
使用 inline
声明的函数,在编译时将会拷贝到调用的地方。
inline function
定义一个sum
函数计算两个数的和。
funmain(args: Array) {
println(sum(1, 2))
}
funsum(a: Int, b: Int): Int {
return a + b
}
复制代码
反编译为 Java 代码:
publicstaticfinalvoidmain(@NotNull String[] args){
int var1 = sum(1, 2);
(var1);
}
publicstaticfinalintsum(int a, int b){
return a + b;
}
复制代码
正常的样子,在该调用的地方调用函数。
然后为 sum
函数添加 inline
声明:
inlinefunsum(a: Int, b: Int): Int {
return a + b
}
复制代码
再反编译为 Java 代码:
publicstaticfinalvoidmain(@NotNull String[] args){
byte a$iv = 1;
int b$iv = 2;
int var4 = a$iv + b$iv;
(var4);
}
publicstaticfinalintsum(int a, int b){
return a + b;
}
复制代码
sum
函数的实现代码被直接拷贝到了调用的地方。
上面两个使用实例并没有体现出 inline
的优势。当你的函数中有 lambda
形参时,inline
的优势才会体现。
inline function with lambda parameters
考虑如下代码,会被编译成怎样的 Java 代码?
funsum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int {
val r = a + b
(r)
return r
}
funmain(args: Array) {
sum(1, 2) { println("Result is: $it") }
}
复制代码
反编译为 Java:
publicstaticfinalintsum(int a, int b, @NotNull Function1 lambda){
int r = a + b;
(r);
return r;
}
publicstaticfinalvoidmain(@NotNull String[] args){
sum(1, 2, (Function1)null.INSTANCE);
}
复制代码
(Function1)
,是由于反编译器工具在找不到等效的 Java 类时的显示的结果。
我传递的那个 lambda
被转换为 Function1
类型,它是 Kotlin 函数(包)的一部分,它以 1 结尾是因为我们在 lambda
函数中传递了一个参数(result:Int
)。
再考虑如下代码:
funmain(args: Array) {
for (i in0..10) {
sum(1, 2) { println("Result is: $it") }
}
}
复制代码
我在循环中调用 sum
函数,每次传递一个 lambda
打印结果。反编译为 Java:
for(byte var2 = 10; var1 Unit = { println(it) }
for (i in0..10) {
sum(1, 2, l)
}
复制代码
反编译为 Java:
Function1 l = (Function1)null.INSTANCE;
int var2 = 0;
for(byte var3 = 10; var2 Unit): Int {
val r = a + b
(r)
return r
}
复制代码
反编译为 Java:
publicstaticfinalvoidmain(@NotNull String[] args){
int var1 = 0;
for(byte var2 = 10; var1 Unit) {
println("Title: $title")
}
privateinlinefuntest(l: () -> Unit) {
println("Title: $title")
}
}
复制代码
注意程序控制流
当使用 inline
时,如果传递给 inline
函数的 lambda
,有 return
语句,那么会导致闭包的调用者也返回。
例子:
inlinefunsum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int {
val r = a + b
(r)
return r
}
funmain(args: Array) {
println("Start")
sum(1, 2) {
println("Result is: $it")
return
}
println("Done")
}
复制代码
反编译 Java:
publicstaticfinalvoidmain(@NotNull String[] args){
String var1 = "Start";
(var1);
byte a$iv = 1;
int b$iv = 2;
int r$iv = a$iv + b$iv;
String var7 = "Result is: " + r$iv;
(var7);
}
复制代码
反编译之后也能看到,lambda
return
之后的代码不会执行。
如何避免?
可以使用 return@label
语法,返回到 lambda
被调用的地方。
funmain(args: Array) {
println("Start")
sum(1, 2) {
println("Result is: $it")
return@sum
}
println("Done")
}
复制代码
noinline
当一个 inline
函数中,有多个 lambda
作为参数时,可以在不想内联的 lambda
前使用 noinline
声明.
inlinefunsum(a: Int, b: Int, lambda: (result: Int) -> Unit, noinline lambda2: (result: Int) -> Unit): Int {
val r = a + b
(r)
(r)
return r
}
funmain(args: Array) {
sum(1, 2,
{ println("Result is: $it") },
{ println("Invoke lambda2: $it") }
)
}
复制代码
反编译 Java:
publicstaticfinalintsum(int a, int b, @NotNull Function1 lambda, @NotNull Function1 lambda2){
int r = a + b;
(r);
(r);
return r;
}
publicstaticfinalvoidmain(@NotNull String[] args){
byte a$iv = 1;
byte b$iv = 2;
Function1 lambda2$iv = (Function1)null.INSTANCE;
int r$iv = a$iv + b$iv;
String var8 = "Result is: " + r$iv;
(var8);
lambda2$(r$iv);
}
复制代码
第一个 lambda
内联到了调用处,而第二个使用 noinline
声明的 lambda
没有。
crossinline
声明一个 lambda
不能有 return
语句(可以有 return@label
语句)。这样可以避免使用 inline
时,lambda
中的 return
影响程序流程。
inlinefunsum(a: Int, b: Int, crossinline lambda: (result: Int) -> Unit): Int {
val r = a + b
(r)
return r
}
funmain(args: Array) {
sum(1, 2) {
println("Result is: $it")
return
}
}
复制代码
总结
- 使用
inline
,内联函数到调用的地方,能减少函数调用造成的额外开销,在循环中尤其有效 - 使用
inline
能避免函数的lambda
形参额外创建Function
对象 - 使用
noinline
可以拒绝形参lambda
内联 - 使用
crossinline
显示声明inline
函数的形参lambda
不能有return
语句,避免lambda
中的return
影响外部程序流程