首先来看段Java代码
代码语言:javascript复制import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
String s1 = "hello";
Consumer<String> c =
s2 -> {
System.out.println(s1);
System.out.println(s2);
};
c.accept("world");
}
}
看下其对应的字节码
代码语言:javascript复制➜ javac Test.java
➜ javap -p -v Test
...
{
...
public static void main(java.lang.String[]);
...
0: ldc #2 // String hello
2: astore_1
3: aload_1
4: invokedynamic #3, 0 // InvokeDynamic #0:accept:(Ljava/lang/String;)Ljava/util/function/Consumer;
9: astore_2
10: aload_2
11: ldc #4 // String world
13: invokeinterface #5, 2 // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
18: return
...
private static void lambdamain0(java.lang.String, java.lang.String);
...
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: return
...
}
...
在上面的字节码中,我们可以看到一个名为 lambdamain0 的方法,该方法是在编译阶段自动生成的,其对应于示例源码中的lambda方法体。
在main方法的字节码中,invokedynamic是整个lambda实现的关键,不过由于该字节码在JVM中的实现逻辑非常复杂,在这里我们就不看具体代码了,只说下大致思路。
该字节码的最终目的是为了创建一个对象,且该对象要实现java.util.function.Consumer接口,这样这个对象才可以赋值给上面示例源码中的 Consumer<String> c 变量。
那这个对象对应的类是哪里来的呢?对,也是动态生成的。
JVM在执行invokedynamic字节码时,会根据class文件中提供的各种信息,调用java.lang.invoke.LambdaMetafactory.metafactory方法来动态生成这个类。
该类的生成结果我们可以通过下面的方式看下
➜ java -Djdk.internal.lambda.dumpProxyClasses Test hello world
执行完上面的命令后,会在当前目录生成一个名为 Test$Lambda1.class 的文件,该文件的内容就是invokedynamic字节码动态生成的类,我们反编译看下
代码语言:javascript复制import java.lang.invoke.LambdaForm.Hidden;
import java.util.function.Consumer;
// $FF: synthetic class
final class Test$Lambda1 implements Consumer {
private final String arg$1;
private Test$Lambda1(String var1) {
this.arg$1 = var1;
}
private static Consumer get$Lambda(String var0) {
return new Test$Lambda1(var0);
}
@Hidden
public void accept(Object var1) {
Test.lambdamain0(this.arg
}
}
恩,正如我们所想,该类确实是实现了java.util.function.Consumer接口。
在该类生成完毕之后,invokedynamic字节码会调用其getLambda方法来创建一个TestLambda1实例,该实例创建成功,也意味着invokedynamic字节码执行完毕。
该实例接着被赋值给了Consumer<String> c 变量,之后调用其accept方法,而在accept方法中又调用了编译阶段生成的Test.lambda
之后的流程就是一般的Java执行流程了,在此不做过多介绍。
依据上面的示例,我们再来总结下lambda是如何实现的:
在编译阶段,javac会自动生成一个lambdamain0方法,该方法对应了lambda的方法体。
在运行阶段,当执行invokedynamic字节码时,JVM会根据编译阶段提供的各种信息,调用java.lang.invoke.LambdaMetafactory.metafactory方法动态生成Test$Lambda1类,该类实现了java.util.function.Consumer接口,且在其accept方法中直接调用了编译阶段生成的lambdamain0方法。
当TestLambda当Test类生成完毕后,invokedynamic字节码会调用其get$Lambda方法,生成一个TestLambda
该实例创建完毕也意味着invokedynamic字节码执行完毕。
该实例创建完毕之后,赋值给了示例源码中的Consumer<String> c变量,然后调用其accept方法,传入world字符串。
在Test$Lambda1类中的accept方法中,其直接调用了编译阶段生成的Test.lambdamain0方法,传入参数变量为arg
Test.lambdamain0方法最终执行了lambda的方法体代码。
至此,整个流程结束。