问题来源
Kotlin每声明一个Lambda表达式,就会在字节码中产生一个匿名类,该匿名类包含了一个invoke方法,作为Lambda的调用方法,每次调用的时候,还会创建一个新对象。
解决方案
为了解决Lambda的开销问题。Kotlin引入了内联函数(inline),内联函数在编译期被嵌入每一个被调用的地方,以减少额外生成的匿名函数,以及函数执行的时间开销。
案例
未使用inline声明lambda
class LambdaDemo {
fun main(args: Array<String>) {
foo {
print("哈哈哈哈哈哈")
}
}
fun foo(block:()->Unit) {
println("before block")
block()
println("after block")
}
}
字节码
public final class LambdaDemo {
public final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
this.foo((Function0)null.INSTANCE);
}
public final void foo(@NotNull Function0 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
String var2 = "before block";
boolean var3 = false;
System.out.println(var2);
block.invoke();
var2 = "after block";
var3 = false;
System.out.println(var2);
}
}
使用inline声明lambda
class LambdaDemo {
fun main(args: Array<String>) {
foo {
print("哈哈哈哈哈哈")
}
}
inline fun foo(block:()->Unit) {
println("before block")
block()
println("after block")
}
}
字节码
public final class LambdaDemo {
public final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
int $i$f$foo = false;
String var4 = "before block";
boolean var5 = false;
System.out.println(var4);
int var6 = false;
String var7 = "哈哈哈哈哈哈";
boolean var8 = false;
System.out.print(var7);
var4 = "after block";
var5 = false;
System.out.println(var4);
}
public final void foo(@NotNull Function0 block) {
int $i$f$foo = 0;
Intrinsics.checkParameterIsNotNull(block, "block");
String var3 = "before block";
boolean var4 = false;
System.out.println(var3);
block.invoke();
var3 = "after block";
var4 = false;
System.out.println(var3);
}
}
注意
- 由于JVM对普通函数已经能够根据实际情况智能地判断是否进行内联优化,所以我们并不需要对其使用Kotlin的inline语法,那只会让字节码变得更加复杂。
- 尽量避免对具有大量函数体的函数进行内联,这样会导致过多的字节码数量。
- 一旦一个函数被定义未内联函数,便不能获取闭包类的私有成员,除非你把他们生命为internal。
- 上述例子中表明:如果在一个函数的开头加上一个inline修饰符,那么它的函数体及Lambda参数都会被内联。然而现实中的情况比较复杂,有一种可能是函数需要接收多个参数,但我们只想对其中部分Lambda参数内联,其他的则不内联,此时可以使用noinline关键字。
非局部返回:正常的Lambda表达式中不允许存在return关键字,如果声明Lambda表达式的时候加上inline关键字,可以达到非局部返回的效果(就是调用Lambda表达式的地方直接全局了,而不是局部返回)
crollinline: 非局部返回可能存在危险,因为有时候,我们内联的函数所接收的Lambda参数常常来自于上下文其他地方。为了避免带有return的Lambda参数产生破坏,可以使用crossinline关键字来修饰该参数。
fun main(args: Array<String>) {
foo {
return // 此处报错,'return' is not allowed here
}
}
inline fun foo(crossinline block:()->Unit) {
println("before block")
block()
println("after block")
}
让泛型不被擦除