写点什么

聊聊 Kotlin 中的 lambda

作者:北洋
  • 2022 年 4 月 25 日
  • 本文字数:1744 字

    阅读完需:约 6 分钟

聊聊Kotlin中的lambda

聊聊 Kotlin 中的 lambda

本质

kotlin 中的 lambda 使用创建类和调用类实现。

实现原理

将 lambda 定义的方法变成一个 function 类,其 invoke 方法体内容也就是 lambda 的方法体。


在 lambda 执行的时候会创建这个 function 类再调用其 invoke 方法实现。

代码演示

fun main(args: Array<String>) {         foo {                println("dive into Kotlin...")          } } fun foo(block: () -> Unit) {     println("before block")         block()         println("end block") }
复制代码


foo 函数使用了 Lambda 表达式,所以我们来看下 foo 函数反编译后的字节码文件:


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

总结

没有觉得的好与坏,了解了实现原理之后再决定使用哪种技术方案符合自己的需求。

发布于: 刚刚阅读数: 2
用户头像

北洋

关注

Android开发 2021.05.25 加入

记录Android学习之路 分享读书心得体会~

评论

发布
暂无评论
聊聊Kotlin中的lambda_kotlin_北洋_InfoQ写作社区