0%

Java8 Lambda表达式

Java8 Lambda表达式有什么限制?

Java8的lambda和匿名类可以实现类似闭包的功能:它们可以作为参数传递给方法,并且可以访问其作用域之外的变量。

但有一个限制:它们不能修改定义Lambda方法的局部变量的内容。这些变量必须是隐式final的。

之所以会有这些限制,主要是由于局部变量是保存在栈上的,如果Lambda可以直接访问局部变量,且Lambda是在另一个线程使用的,那么使用Lambda的线程可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上访问的是它的副本,而如果局部变量仅会赋值一次那就没什么区别了。

如果允许捕获可变的局部变量,就会引发造成线程不安全的可能性,而这是我们极力希望避免的。基于种种考虑,Java直接在语法层面做出限制,这其实也是不鼓励程序员使用改变外部变量的典型的命令式编程模式。

Java8 Lambda表达式是怎么实现的?

对lambda表达式里的代码,生成静态私有方法,创建一个函数式接口类的实例来调用这个私有静态方法。

这种方式实现的好处在于:

  1. 使用预定义的函数式接口类,不用新增类,避免新类的加载、验证、解析、初始化的耗时。
  2. lambda表达式代码翻译到字节码的过程,可以动态的改变,方便做统一的优化。

为什么Java 8的Lambda表达式的字节码指令要使用invokedynamic指令?

Java8的设计者之一Brian Goetz对于lambda表达式的设计文档中所提到的方案选择:

  • 一是转换策略要足够灵活以能够应对将来的优化。
  • 二是要维持现有class文件标准的稳定性。

用invokedynamic指令干了什么?

invokedynamic调用JDK里的LambdaMetafactory类的方法来把Lambda表达式翻译为字节码。

不用invokedynamic的坏处?

如果把Lambda表达式的翻译方式写死在类的字节码,以后不方便统一修改翻译策略了。

Java8 Lambda表达式为什么不用匿名内部类实现?

Lambda表达式在语法上看起来就跟匿名内部类一样。

但是对于匿名内部类:

  1. 编译器会为每个匿名类生成一个新的class文件。由于每个class文件在使用前都需要经过一系列的加载、验证、解析、初始化的过程,大量的类文件会直接影响应用的启动性能。
  2. 每个新的匿名类都会为类或者接口产生一个新的子类型。如果你为了实现一个比较器,使用了一百多个不同的lambda表达式,这意味着该比较器会有一百多种不同的子类型。这种情况下,JVM的运行时性能调优会变得更加困难。

为什么Java8的特性需要Android Gradle插件特定版本才能支持?

Android系统上,Java-Bytecode(JVM字节码)是不能直接运行在Android系统上的,需要转换成Android-Bytecode(Dalvik/ART 字节码)。

为什么呢?比如Java 8的Lambda表达式是通过invokedynamic指令实现的,Android的dex编译器不支持invokedynamic指令,导致Android不能直接支持Java 8。

既然不能直接支持,那就只能在Java-Bytecode转换到Android-Bytecode这一过程中想办法,间接支持。这个间接支持的过程我们统称为Desugar(脱糖)过程。

具体是怎么实现的呢?比如Java 8的Lambda表达式,javac编译过后,Lambda表达式里的逻辑会被编译为私有静态方法,然后用invokedynamic去在运行时动态创建一个类实例,每个Lambda表达式都会对应一个函数式类接口的,在类方法里调用静态方法。脱糖的过程,就是把invokedynamic指令去除,直接调用静态方法。

脱糖

脱糖即在编译阶段将在语法层面一些底层字节码不支持的特性转换为基础的字节码结构,(比如 List 上的泛型脱糖后在字节码层面实际为 Object); Android 工具链对 Java8 语法特性脱糖的过程可谓丰富多彩,当然他们的最终目的是一致的:使新的语法可以在所有的设备上运行。

D8编译器 已经支持脱糖,让 Java 8 提供的特性(如 lambdas)可以转换成 Java 7 特性。

D8、R8编译器有什么特别?

D8 编译器作为默认的 DEX 字节码文件编译器,具有更好的性能;

R8 作为 ProGuard 的替代工具,用于代码的压缩(shrinking)和混淆(obfuscation);

Java 7-8-9 等等新引入的语言特性并不能直接就能用在 Android 开发中,基本上现在的所有的 Android 开发者还在被困在 Java 6 SE 上。

为了让我们能使用上 Java 8 的特性,Google 使用了 Transformation 来增加了一步编译过程叫 desugaring,其实就是将我们代码里使用的 java 8 新特性翻译为 Dalvik/ART 能够识别的 java 6 字节码。这不可避免会导致一个问题 - 更长的编译时间。

为了解决这个问题,在 Android Studio 3.2 中,Google 使用 D8 替换了旧的 dx 编译器。D8 的主要改进是消除 desuguaring 的过程,让其成为 dex 编译的一部分,从而加快编译速度。

参考资料