你可能会好奇Java编译器是如何实现Lambda表达式,而Java虚拟机又是如何对它们进行处理的。如果你认为Lambda表达式就是简单地被转换为匿名类,那就太天真了,请继续阅读下去。本附录通过审视编译生成的.class文件,简要地讨论Java是如何编译Lambda表达式的。
D.1 匿名类
第2章已经介绍过,匿名类可以同时声明和实例化一个类。因此,它们和Lambda表达式一样,也能用于提供函数式接口的实现。
由于Lambda表达式提供了函数式接口中抽象方法的实现,这让人有一种感觉,似乎在编译过程中让Java编译器直接将Lambda表达式转换为匿名类更直观。不过,匿名类有着种种不尽如人意的特性,会给应用程序的性能带来负面影响。
编译器会为每个匿名类生成一个新的.class文件。这些新生成的类文件的文件名通常以ClassName$1这种形式呈现,其中ClassName是匿名类出现的类的名字,紧跟着一个美元符号和一个数字。生成大量的类文件是不利的,因为每个类文件在使用之前都需要加载和验证,这会直接影响应用的启动性能。如果将Lambda表达式转换为匿名类,那么每个Lambda表达式都会产生一个新的类文件,这是我们不期望发生的。
每个新的匿名类都会为类或者接口产生一个新的子类型。如果你为了实现一个比较器,使用了一百多个不同的Lambda表达式,这意味着该比较器会有一百多个不同的子类型。这种情况下,JVM的运行时性能调优会变得更加困难。
D.2 生成字节码
Java的源代码文件会经由Java编译器编译为Java字节码。之后JVM可以执行这些生成的字节码运行应用。编译时,匿名类和Lambda表达式使用了不同的字节码指令。你可以通过下面这条命令查看任何类文件的字节码和常量池:
javap -c -v ClassName
我们试着使用Java 7中旧的格式实现了Function接口的一个实例,代码如下所示。
代码清单 D-1 以匿名内部类的方式实现的一个Function接口
import java.util.function.Function; public class InnerClass { Function