1.1 目标
了解Lambda的实现原理
我们现在已经会使用Lambda表达式了。现在同学们肯定很好奇Lambda是如何实现的,现在我们就来探究Lambda
表达式的底层实现原理。
@FunctionalInterface
interface Swimmable {
public abstract void swimming();
}
public class Demo04LambdaImpl {
public static void main(String[] args) {
// 匿名内部类在编译后会形成一个新的类.$
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("使用匿名内部类实现游泳");
}
});
}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
我们可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class
使用XJad反编译这个类,得到如下代码:
package com.itheima.demo01lambda;
import java.io.PrintStream;
// Referenced classes of package com.itheima.demo01lambda:
// Swimmable, Demo04LambdaImpl
static class Demo04LambdaImpl$1 implements Swimmable {
public void swimming(){
System.out.println("使用匿名内部类实现游泳");
}
Demo04LambdaImpl$1() {
}
}
我们再来看看Lambda的效果,修改代码如下:
public class Demo04LambdaImpl {
public static void main(String[] args) {
goSwimming(() -> {
System.out.println("Lambda游泳");
});
}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一
个新的类。使用XJad对这个类进行反编译,发现XJad报错。使用了Lambda后XJad反编译工具无法反编译。我们使用
JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。
在DOS命令行输入:
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员
反汇编后效果如下:
C:\Users\>javap -c -p Demo04LambdaImpl.class
Compiled from "Demo04LambdaImpl.java"
public class com.itheima.demo01lambda.Demo04LambdaImpl {
public com.itheima.demo01lambda.Demo04LambdaImpl();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:
()Lcom/itheima/demo01lambda/Swimmable;
5: invokestatic #3 // Method goSwimming:
(Lcom/itheima/demo01lambda/Swimmable;)V
8: return
public static void goSwimming(com.itheima.demo01lambda.Swimmable);
Code:
0: aload_0
1: invokeinterface #4, 1 // InterfaceMethod
com/itheima/demo01lambda/Swimmable.swimming:()V
6: return
private static void lambda$main$0();
Code:
0: getstatic #5 // Field
java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Lambda游泳
5: invokevirtual #7 // Method java/io/PrintStream.println:
(Ljava/lang/String;)V
8: return
}
可以看到在类中多出了一个私有的静态方法 lambda$main$0 。这个方法里面放的是什么内容呢?我们通过断点调试
来看看:
可以确认 lambda$main$0 里面放的就是Lambda中的内容,我们可以这么理解 lambda$main$0 方法:
public class Demo04LambdaImpl {
public static void main(String[] args) {
...
}
private static void lambda$main$0() {
System.out.println("Lambda游泳");
}
}
关于这个方法 lambda$main$0 的命名:以lambda开头,因为是在main()函数里使用了lambda表达式,所以带有
$main表示,因为是第一个,所以$0。
如何调用这个方法呢?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加
上 -Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文
件中。使用java命令如下:
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
根据上面的格式,在命令行输入以下命令:
C:\Users>java -Djdk.internal.lambda.dumpProxyClasses
com.itheima.demo01lambda.Demo04LambdaImpl
Lambda游泳
执行完毕,可以看到生成一个新的类,效果如下:
反编译 Demo04LambdaImpl$$Lambda$1.class 这个字节码文件,内容如下:
// Referenced classes of package com.itheima.demo01lambda:
// Swimmable, Demo04LambdaImpl
final class Demo04LambdaImpl$$Lambda$1 implements Swimmable {
public void swimming(){
Demo04LambdaImpl.lambda$main$0();
}
private Demo04LambdaImpl$$Lambda$1(){
}
}
可以看到这个匿名内部类实现了 Swimmable 接口,并且重写了 swimming 方法, swimming 方法调用
Demo04LambdaImpl.lambda$main$0() ,也就是调用Lambda中的内容。最后可以将Lambda理解为:
public class Demo04LambdaImpl {
public static void main(String[] args) {
goSwimming(new Swimmable() {
public void swimming() {
Demo04LambdaImpl.lambda$main$0();
}
});
}
private static void lambda$main$0() {
System.out.println("Lambda表达式游泳");
}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
1.2 小结
匿名内部类在编译的时候会一个class文件
Lambda在程序运行的时候形成一个类
1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
2. 还会形成一个匿名内部类,实现接口,重写抽象方法
3. 在接口的重写方法中会调用新生成的方法.