前言

读过上一篇之后,相信对Lambda表达式的语法以及基本原理有了一定了解。对于编写代码,有这些知识已经够用。本文将进一步区分Lambda表达式和匿名内部类在JVM层面的区别,如果对这一部分不感兴趣,可以跳过

不是匿名内部类的简写

经过第一篇的的介绍,我们看到Lambda表达式似乎只是为了简化匿名内部类书写,这看起来仅仅通过语法糖在编译阶段把所有的Lambda表达式替换成匿名内部类就可以了。但实时并非如此。在JVM层面,Lambda表达式和匿名内部类有着明显的差别。

匿名内部类实现

匿名内部类仍然是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。因此如果有如下形式的代码,编译之后将会产生两个class文件:

  1. public class MainAnonymousClass {
  2. public static void main(String[] args) {
  3. new Thread(new Runnable(){
  4. @Override
  5. public void run(){
  6. System.out.println("Anonymous Class Thread run()");
  7. }
  8. }).start();;
  9. }
  10. }

编译之后文件分布如下,两个class文件分别是主类和匿名内部类产生的:
Lambda表达式的实现原理 - 图1
进一步分析主类MainAnonymousClass.class的字节码,可发现其创建了匿名内部类的对象:

  1. // javap -c MainAnonymousClass.class
  2. public class MainAnonymousClass {
  3. ...
  4. public static void main(java.lang.String[]);
  5. Code:
  6. 0: new #2 // class java/lang/Thread
  7. 3: dup
  8. 4: new #3 // class MainAnonymousClass$1 /*创建内部类对象*/
  9. 7: dup
  10. 8: invokespecial #4 // Method MainAnonymousClass$1."<init>":()V
  11. 11: invokespecial #5 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
  12. 14: invokevirtual #6 // Method java/lang/Thread.start:()V
  13. 17: return
  14. }

Lambda表达式实现

Lambda表达式通过invokedynamic指令实现,书写Lambda表达式不会产生新的类。如果有如下代码,编译之后只有一个class文件:

  1. public class MainLambda {
  2. public static void main(String[] args) {
  3. new Thread(
  4. () -> System.out.println("Lambda Thread run()")
  5. ).start();;
  6. }
  7. }

编译之后的结果:
Lambda表达式的实现原理 - 图2
通过javap反编译命名,我们更能看出Lambda表达式内部表示的不同:

  1. // javap -c -p MainLambda.class
  2. public class MainLambda {
  3. ...
  4. public static void main(java.lang.String[]);
  5. Code:
  6. 0: new #2 // class java/lang/Thread
  7. 3: dup
  8. 4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; /*使用invokedynamic指令调用*/
  9. 9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
  10. 12: invokevirtual #5 // Method java/lang/Thread.start:()V
  11. 15: return
  12. private static void lambda$main$0(); /*Lambda表达式被封装成主类的私有方法*/
  13. Code:
  14. 0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
  15. 3: ldc #7 // String Lambda Thread run()
  16. 5: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  17. 8: return
  18. }

反编译之后我们发现Lambda表达式被封装成了主类的一个私有方法,并通过invokedynamic指令进行调用。

推论,this引用的意义

既然Lambda表达式不是内部类的简写,那么Lambda内部的this引用也就跟内部类对象没什么关系了。在Lambda表达式中this的意义跟在表达式外部完全一样。因此下列代码将输出两遍Hello Hoolee,而不是两个引用地址。

  1. public class Hello {
  2. Runnable r1 = () -> { System.out.println(this); };
  3. Runnable r2 = () -> { System.out.println(toString()); };
  4. public static void main(String[] args) {
  5. new Hello().r1.run();
  6. new Hello().r2.run();
  7. }
  8. public String toString() { return "Hello Hoolee"; }
  9. }

参考文献

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

1.编译时,Java代码中的lambada表达式将使所在类的生成逻辑相同的私有方法,如果lambda表达式捕捉了this,则生成的是实例方法;否则,生成静态方法。生成的方法的参数取决于捕获的变量,每个捕获的变量都对应一个参数。 2.lambda表达式所在位置将生成invokedynamic指令,该invokedynamic指令的调用结果是返回一个代理对象,这个对象实现了函数式接口,其内部将转发调用第1步生成的私有方法。而这个代理对象是由invokedynamic指令的bootstrap方法来生成的,并封装到CallSite对象中的。这个代理对象的构造函数也是取决于捕获的外部变量类型和数量。换言之,每个lambda表达式本质上是一个“工厂方法”,返回实现了对应函数式接口的实例。 https://blog.csdn.net/raintungli/article/details/54910152