在我的想法里,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表达式的执行原理,已经粗浅的解释完毕了。