在我的想法里,Lambda长得很奇怪,虚拟机真的认识这玩意吗?还是说,Lambda经过编译后,脱掉了伪装的衣服,变成了大家熟知的方法?
对Lambda不熟悉的同学,可以先看我的另外两篇文章。
【JAVA8】对Lambda的理解
【JAVA8】快速理解Consumer、Supplier、Predicate与Function
先看以下的一个示例:
//使用注解@FunctionalInterface来声明这是一个函数式接口
@FunctionalInterface
interface Print {
void output(String str);
}
public class Main {
private static void handle(String str, Print p) { p.output(str); } public static void main(String[] args) { handle("abc", str -> System.out.println(str)); }
}
运行后,显然输出abc。
那么,这段代码被编译成什么样子了呢,我们使用javap -p Main.class查看编译后的类成员信息(-p显示所有的类和成员)
public class com.yang.testLambda.Main {
public com.yang.testLambda.Main();
private static void handle(java.lang.String, com.yang.testLambda.Print);
public static void main(java.lang.String[]);
private static void lambda0(java.lang.String);
}
可以看到,多出来一个私有静态方法lambda0
那这个静态方法,里面的内容又是什么呢?
继续使用javap -c -p Main.class(-c对代码进行反汇编)看看
public class com.yang.testLambda.Main {
public com.yang.testLambda.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return</init>
private static void handle(java.lang.String, com.yang.testLambda.Print);
Code:
0: aload_1
1: aload_0
2: invokeinterface #2, 2 // InterfaceMethod com/yang/testLambda/Print.output:(Ljava/lang/String;)V
7: return
public static void main(java.lang.String[]);
Code:
0: ldc #3 // String abc
2: invokedynamic #4, 0 // InvokeDynamic #0:output:()Lcom/yang/testLambda/Print;
7: invokestatic #5 // Method handle:(Ljava/lang/String;Lcom/yang/testLambda/Print;)V
10: return
private static void lambda0(java.lang.String);
Code:
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: return
}
只看lambda0方法,发现里面是
new PrintStream("abc").println();
也就是
System.out.println("abc")
说明,生成的私有静态方法里面的内容就是lambda表达式里面的主要内容。
那么,这个私有静态方法,是何时何地被谁调用的呢?
现在需要使用javac Main.java编译成Main.class文件,之后使用java -Djdk.internal.lambda.dumpProxyClasses Main来运行,并会将运行过程中产生的内部类输出出来。
运行第一个命令后,会产生Main.class和Print.class文件
运行第二个命令后,会额外产生Main$1.class文件
使用javap -c -p Main$1.class反编译Main$1.class文件,会得到
final class Main$1 implements Print {
private Main$1();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return</init>
public void output(java.lang.String);
Code:
0: aload_1
1: invokestatic #18 // Method Main.lambda0:(Ljava/lang/String;)V
4: return
}
发现Main$1实现了Print接口,并且output方法中,调用了Main类中的私有静态方法lambda0
那么,该内部类又是何时何地被谁调用的呢?
而在一开始我们使用javap -c-p Main.class时,其中主方法是:
public static void main(java.lang.String[]);
Code:
0: ldc #3 // String abc
2: invokedynamic #4, 0 // InvokeDynamic #0:output:()Lcom/yang/testLambda/Print;
7: invokestatic #5 // Method handle:(Ljava/lang/String;Lcom/yang/testLambda/Print;)V
10: return
可以看得出这边使用了invokedynamic调用了函数式接口,可以粗略的认为这里实例化了Print的实现类(其实具体的逻辑太挺复杂,这里直接简化了),就是内部类Main$1,然后调用静态方法handle,这个方法接收一个字符串和Print实现类实例。
那么,一开始的lambda表达式,可以改写成这样的形式:
interface Print {
void output(String str);
}
public class Main {
private static void handle(String str, Print p) { p.output(str); } //编译后生成的私有静态方法,方法内容就是lambda里的内容 private static void lambda$main$0(String str) { System.out.println(str); } //运行时生成的内部类,实现函数式接口,实现方法中调用私有静态方法 final class Main$$Lambda$1 implements Print { @Override public void output(String str) { lambda$main$0(str); } } public static void main(String[] args) { Print print = new Main().new Main$$Lambda$1(); handle("abc", print); }
}
到这里,lambda表达式的执行原理,已经粗浅的解释完毕了。