聊聊Kotlin中的lambda
本质
kotlin中的lambda使用创建类和调用类实现。
实现原理
将lambda定义的方法变成一个function类,其invoke方法体内容也就是lambda的方法体。
在lambda执行的时候会创建这个function类再调用其invoke方法实现。
代码演示
代码语言:javascript复制fun main(args: Array<String>) {
foo {
println("dive into Kotlin...")
}
}
fun foo(block: () -> Unit) {
println("before block")
block()
println("end block")
}
foo函数使用了Lambda表达式,所以我们来看下foo函数反编译后的字节码文件:
代码语言:javascript复制public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
//可以看到Lambda表达式变成了创建一个Function0类并传入方法中
foo((Function0)null.INSTANCE);
}
//该方法参数变成了一个Function类
public static final void foo(@NotNull Function0 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
String var1 = "before block";
System.out.println(var1);
//调用invoke方法也就是Lambda中的方法体
block.invoke();
var1 = "end block";
System.out.println(var1);
}
缺点
这种方法如果放在多个中间操作符使用lambda的时候会非常耗费性能。
ps:因为中间操作符大多使用lambda表达式,是因为它够简单易懂,当调用个lambda表达式时也就会对应的创建多个Functon类和调用其invoke方法
优化方案
java的Lambda背景
java中为什么没有用这种方式实现呢:创建类调用其invoke方法。
其实java中是通过invokedynamic指令实现的,其本质是运行时进行替换对应的Lambda中的代码
好处
1.运行时替换,相比较Kotlin直接写死创建类的方式性能更好 2.在class文件中只有这个指令,不像kotlin需要设置很多不必要的信息
Kotlin的优化
为什么不去直接使用java的那个指令呢?
原因很简单,Kotlin是基于java 6开发的,而java的这个优化是在java 7才出现的,所以为了兼容只能另辟蹊径。
虽然无法实现,但是我们可以借鉴java中的做法对吧?
他是运行时进行替换方法的,所以本质是替换方法。也就是改变思路:之前我们是将表达式封装到一个类的方法中,具体调用lambda的时候调用这个类的方法 把它换成 我们直接把lambda中的方法体复制到调用方的方法体里面。
java是运行时替换,我们直接编译期间就替换了,一样的效果,而且kotlin把替换操作提前到编译期性能还会好一点
好了,说完上面的思路原理,我们再来看Kotlin该如何使用这种方法呢?
inline关键字
Kotlin中可以使用inline将inline修饰的方法复制到调用方。被inline修饰的方法也叫作内联函数。
使用场景:集合中的中间操作符都会使用inline,当然也可以指定不内联的参数方法,使用noinline修饰这个方法参数。
内联特点
非局部返回
非局部返回:顾名思义全局针对的不是当前方法体
return只作用于当前的函数体。
1.如果lambda中写return编译会报错,lambda中不允许写return关键字。但是可以通过给调用的方法加入inline关键字,将方法复制到调用方这样就可以return了因为他就是很正常的返回,但是结果会变得不一样,因为处于同一个方法体return之后的语句不会执行,这也叫做非局部返回。这种方式在集合遍历中非常有用。由于集合api都是inline的函数所以return后直接返回不在遍历之后元素
2.第二种方式也就是使用@,@方法名。可以指定returh作用的方法体
具体化参数类型refried
参数类型:可以猜到对应的就是泛型。那么具体化又是什么意思呢?
java和kotlin一样都是在运行时类型擦除的所有我们无法获取到泛型的具体类型。
eg:我们只能获取到List< E >,而无法获取到List< String >
但是上面说到inline关键字是将inline方法体复制到调用方的方法体中,所以其传入的参数我们是可以知道具体类型的。
在inline修饰的方法中使用refried关键字修饰泛型去打印这个泛型的class发现确实可以获取到具体类型。
inline的缺点
我们了解了它实现的原理就是复制方法体到调用的地方,那么缺点也可想而知:
- inline修饰的方法体过多,导致调用方的方法体庞大
- 内联函数无法获得闭包类的私有成员,除非声明为internal
总结
没有觉得的好与坏,了解了实现原理之后再决定使用哪种技术方案符合自己的需求。