在JAVA8中,Lamdba表达式通过invokedynamic指令实现的,通过invokedynamic可以避免编译期硬编码生成内部匿名类的实现,而是由JIT在运行时才产生相应的接入点代码,显著减少静态生成的类和字节码大小。
由于Kotlin支持JAVA6,Kotlin对于Lamdba表达式的支持不得不通过编译期硬编码的方式实现,导致大量的内部匿名类,为了避免此问题,Kotlin引入inline、noinline、crossinline等关键字,以减少额外生成的匿名类数以及函数执行的时间开销。
本文将分析inline、noinline、crossinline关键字的字节码实现。
fun main() {
fun3{
println("lambda1")
}
}
fun fun3(funparam: () -> Unit) {
println("fun1 starting")
funparam()
println("fun1 end")
}
上文是一个简单的Lamdba的调用,正如文中所描述的,编译器会针对Lamdba中的逻辑自动生成一个内部匿名类,外部代码通过调用此内部匿名类的invoke方法执行Lamdba逻辑。
通过下面的例程,我们看看Kotlin的内联是如何操作的。
fun main() {
fun4{
println("lambda1")
}
}
inline fun fun4(funparam: () -> Unit) {
println("fun1 starting")
funparam()
println("fun1 end")
}
执行结果如下:
fun1 starting
lambda1
fun1 end
反编译字节码如下:
public final class T4Kt {
public static final void main();
Code:
0: iconst_0
1: istore_0
2: ldc #11 // String fun1 starting
4: astore_1
5: iconst_0
6: istore_2
7: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
14: iconst_0
15: istore_3
16: ldc #25 // String lambda1
18: astore 4
20: iconst_0
21: istore 5
23: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload 4
28: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
31: nop
32: ldc #27 // String fun1 end
34: astore_1
35: iconst_0
36: istore_2
37: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
40: aload_1
41: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
44: nop
45: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #9 // Method main:()V
3: return
}
使用inline关键字修饰fun4后,编译结果中没有了内部匿名类,Lambda表达式逻辑、fun4就均被内联到public static final void main()方法中 ,程序占用资源减少并且执行效率有提升。
由于方法内联后,内联函数函数体和Lambda逻辑都会直接替代具体的调用,如果Lambda逻辑中包含return语句,则导致非局部返回。
fun main() {
fun5{
println("lambda1")
return
}
}
inline fun fun5(funparam: () -> Unit) {
println("fun1 starting")
funparam()
println("fun1 end")
}
执行结果如下:
fun1 starting
lambda1
我们注意到,最后的"fun1 end"预计没有被执行。
public static final void main();
Code:
0: iconst_0
1: istore_0
2: ldc #11 // String fun1 starting
4: astore_1
5: iconst_0
6: istore_2
7: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
14: iconst_0
15: istore_3
16: ldc #25 // String lambda1
18: astore 4
20: iconst_0
21: istore 5
23: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload 4
28: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
31: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #9 // Method main:()V
3: return
从字节码中也可以看出println(“fun1 end”)语句根本就没有被执行。
非局部返回尤其在循环控制中显得特别有用,比如Kotlin的forEach接口,它接收的就是一个Lambda参数,由于它也是一个内联函数,所以可以直接在它调用的Lambda中执行return退出上一层的程序。
虽然某些场景下非局部返回可能非常有用,但还是可能存在危险,我们可以通过crossline关键字防止非局部返回的发生。
fun main() {
fun5{
println("lambda1")
return
}
}
inline fun fun5(crossinline funparam: () -> Unit) {
println("fun1 starting")
funparam()
println("fun1 end")
}
执行结果是’return’ is not allowed here
出于Kotlin语言定位的考量,Kotlin当前采用方法内联对Lambda带来的额外开销进行优化,而不是JAVA7引入的invokedynamic。
Kotlin的方法内联是编译器硬编码的方式,与JIT的运行动态的方法内联不同,也是不同层次的优化,应当注意区分。