D.2 生成字节码

Java的源代码文件会经由Java编译器编译为Java字节码。之后JVM可以执行这些生成的字节码运行应用。编译时,匿名类和Lambda表达式使用了不同的字节码指令。你可以通过下面这条命令查看任何类文件的字节码和常量池:

  1. javap -c -v ClassName

我们试着使用Java 7中旧的格式实现了Function接口的一个实例,代码如下所示。

代码清单 D-1 以匿名内部类的方式实现的一个Function接口

  1. import java.util.function.Function;
  2. public class InnerClass {
  3. Function<Object, String> f = new Function<Object, String>() {
  4. @Override
  5. public String apply(Object obj) {
  6. return obj.toString();
  7. }
  8. };
  9. }

这种方式下,和Function对应,以匿名内部类形式生成的字节码看起来就像下面这样:

  1. 0: aload_0
  2. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  3. 4: aload_0
  4. 5: new #2 // class InnerClass$1
  5. 8: dup
  6. 9: aload_0
  7. 10: invokespecial #3 // Method InnerClass$1."<init>":(LInnerClass;)V
  8. 13: putfield #4 // Field f:Ljava/util/function/Function;
  9. 16: return

这段代码展示了下面这些编译中的细节。

  • 通过字节码操作new,一个InnerClass$1类型的对象被实例化了。与此同时,一个指向新创建对象的引用会被压入栈。
  • dup操作会复制栈上的引用。
  • 接着,这个值会被invokespecial指令处理,该指令会初始化对象。
  • 栈顶现在包含了指向对象的引用,该值通过putfield指令保存到了LambdaBytecode类的f1字段。

InnerClass$1是由编译器为匿名类生成的名字。如果你想要再次确认这一情况,也可以查看InnerClass$1类文件,你可以看到Function接口的实现代码如下:

  1. class InnerClass$1 implements
  2. java.util.function.Function<java.lang.Object, java.lang.String> {
  3. final InnerClass this$0;
  4. public java.lang.String apply(java.lang.Object);
  5. Code:
  6. 0: aload_1
  7. 1: invokevirtual #3 // Method
  8. java/lang/Object.toString:()Ljava/lang/String;
  9. 4: areturn
  10. }