1.1 目标


了解Lambda的实现原理

我们现在已经会使用Lambda表达式了。现在同学们肯定很好奇Lambda是如何实现的,现在我们就来探究Lambda 表达式的底层实现原理。

  1. @FunctionalInterface
  2. interface Swimmable {
  3. public abstract void swimming();
  4. }
  1. public class Demo04LambdaImpl {
  2. public static void main(String[] args) {
  3. // 匿名内部类在编译后会形成一个新的类.$
  4. goSwimming(new Swimmable() {
  5. @Override
  6. public void swimming() {
  7. System.out.println("使用匿名内部类实现游泳");
  8. }
  9. });
  10. }
  11. public static void goSwimming(Swimmable swimmable) {
  12. swimmable.swimming();
  13. }
  14. }

我们可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class

image.png

使用XJad反编译这个类,得到如下代码:


  1. package com.itheima.demo01lambda;
  2. import java.io.PrintStream;
  3. // Referenced classes of package com.itheima.demo01lambda:
  4. // Swimmable, Demo04LambdaImpl
  5. static class Demo04LambdaImpl$1 implements Swimmable {
  6. public void swimming(){
  7. System.out.println("使用匿名内部类实现游泳");
  8. }
  9. Demo04LambdaImpl$1() {
  10. }
  11. }


我们再来看看Lambda的效果,修改代码如下:

  1. public class Demo04LambdaImpl {
  2. public static void main(String[] args) {
  3. goSwimming(() -> {
  4. System.out.println("Lambda游泳");
  5. });
  6. }
  7. public static void goSwimming(Swimmable swimmable) {
  8. swimmable.swimming();
  9. }
  10. }

运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一 个新的类。使用XJad对这个类进行反编译,发现XJad报错。使用了Lambda后XJad反编译工具无法反编译。我们使用 JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。

在DOS命令行输入:

javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员

反汇编后效果如下:

  1. C:\Users\>javap -c -p Demo04LambdaImpl.class
  2. Compiled from "Demo04LambdaImpl.java"
  3. public class com.itheima.demo01lambda.Demo04LambdaImpl {
  4. public com.itheima.demo01lambda.Demo04LambdaImpl();
  5. Code:
  6. 0: aload_0
  7. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  8. 4: return
  9. public static void main(java.lang.String[]);
  10. Code:
  11. 0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:
  12. ()Lcom/itheima/demo01lambda/Swimmable;
  13. 5: invokestatic #3 // Method goSwimming:
  14. (Lcom/itheima/demo01lambda/Swimmable;)V
  15. 8: return
  16. public static void goSwimming(com.itheima.demo01lambda.Swimmable);
  17. Code:
  18. 0: aload_0
  19. 1: invokeinterface #4, 1 // InterfaceMethod
  20. com/itheima/demo01lambda/Swimmable.swimming:()V
  21. 6: return
  22. private static void lambda$main$0();
  23. Code:
  24. 0: getstatic #5 // Field
  25. java/lang/System.out:Ljava/io/PrintStream;
  26. 3: ldc #6 // String Lambda游泳
  27. 5: invokevirtual #7 // Method java/io/PrintStream.println:
  28. (Ljava/lang/String;)V
  29. 8: return
  30. }


可以看到在类中多出了一个私有的静态方法 lambda$main$0 。这个方法里面放的是什么内容呢?我们通过断点调试 来看看:

image.png
可以确认 lambda$main$0 里面放的就是Lambda中的内容,我们可以这么理解 lambda$main$0 方法:

  1. public class Demo04LambdaImpl {
  2. public static void main(String[] args) {
  3. ...
  4. }
  5. private static void lambda$main$0() {
  6. System.out.println("Lambda游泳");
  7. }
  8. }

关于这个方法 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游泳

执行完毕,可以看到生成一个新的类,效果如下:

image.png

反编译 Demo04LambdaImpl$$Lambda$1.class 这个字节码文件,内容如下:

  1. // Referenced classes of package com.itheima.demo01lambda:
  2. // Swimmable, Demo04LambdaImpl
  3. final class Demo04LambdaImpl$$Lambda$1 implements Swimmable {
  4. public void swimming(){
  5. Demo04LambdaImpl.lambda$main$0();
  6. }
  7. private Demo04LambdaImpl$$Lambda$1(){
  8. }
  9. }

可以看到这个匿名内部类实现了 Swimmable 接口,并且重写了 swimming 方法, swimming 方法调用 Demo04LambdaImpl.lambda$main$0() ,也就是调用Lambda中的内容。最后可以将Lambda理解为:

  1. public class Demo04LambdaImpl {
  2. public static void main(String[] args) {
  3. goSwimming(new Swimmable() {
  4. public void swimming() {
  5. Demo04LambdaImpl.lambda$main$0();
  6. }
  7. });
  8. }
  9. private static void lambda$main$0() {
  10. System.out.println("Lambda表达式游泳");
  11. }
  12. public static void goSwimming(Swimmable swimmable) {
  13. swimmable.swimming();
  14. }
  15. }

1.2 小结

匿名内部类在编译的时候会一个class文件

Lambda在程序运行的时候形成一个类

1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
2. 还会形成一个匿名内部类,实现接口,重写抽象方法
3. 在接口的重写方法中会调用新生成的方法.