前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java lambda 原理分析

java lambda 原理分析

作者头像
luoxn28
发布2019-11-06 17:39:36
7540
发布2019-11-06 17:39:36
举报
文章被收录于专栏:TopCoder
Java lambda 一眼看上去有点像匿名内部类的简化形式,但是二者确有着本质的差别。匿名内部类经编译后会生成对应的class文件,格式为XXX$n.class;而lambda代码经过编译后生成一个private方法,方法名格式为lambda$main$n
代码语言:javascript
复制
// Application.main 方法中代码
ArrayList<String> list = CollectionUtil.newArrayList("I", "love", "you");
list.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});
list.forEach(System.out::println);

以上代码就会产生一个Application$1.class文件和一个lambda$main$0的方法。既然lambda实现不是内部类,那么在lambda中this就代表的当前所在类实例。

代码语言:javascript
复制
// Application.main 方法中代码
ArrayList<String> list = CollectionUtil.newArrayList("I", "love", "you");
list.forEach(item -> {
    System.out.println(item);
});

通过javap -c -p Application.class查看以上代码对应的字节码:

代码语言:javascript
复制
Constant pool:
   #1 = Methodref          #12.#36        // java/lang/Object."<init>":()V
   #2 = Class              #37            // java/lang/String
   #3 = String             #38            // I
   #4 = String             #39            // love
   #5 = String             #40            // you
   #6 = Methodref          #41.#42        // cn/hutool/core/collection/CollectionUtil.newArrayList:([Ljava/lang/Object;)Ljava/util/ArrayList;
   #7 = InvokeDynamic      #0:#48         // #0:accept:()Ljava/util/function/Consumer;
   #8 = Methodref          #49.#50        // java/util/ArrayList.forEach:(Ljava/util/function/Consumer;)V
   #9 = Fieldref           #51.#52        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #53.#54        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Class              #55            // com/luo/demo/Application
  #12 = Class              #56            // java/lang/Object
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Lcom/luo/demo/Application;
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               args
  #23 = Utf8               [Ljava/lang/String;
  #24 = Utf8               list
  #25 = Utf8               Ljava/util/ArrayList;
  #26 = Utf8               LocalVariableTypeTable
  #27 = Utf8               Ljava/util/ArrayList<Ljava/lang/String;>;
  #28 = Utf8               lambda$main$0
  #29 = Utf8               (Ljava/lang/String;)V
  #30 = Utf8               item
  #31 = Utf8               Ljava/lang/String;
  #32 = Utf8               SourceFile
  #33 = Utf8               Application.java
  #34 = Utf8               RuntimeVisibleAnnotations
  #35 = Utf8               Lorg/springframework/boot/autoconfigure/SpringBootApplication;
  #36 = NameAndType        #13:#14        // "<init>":()V
  #37 = Utf8               java/lang/String
  #38 = Utf8               I
  #39 = Utf8               love
  #40 = Utf8               you
  #41 = Class              #57            // cn/hutool/core/collection/CollectionUtil
  #42 = NameAndType        #58:#59        // newArrayList:([Ljava/lang/Object;)Ljava/util/ArrayList;
  #43 = Utf8               BootstrapMethods
  #44 = MethodHandle       #6:#60         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #45 = MethodType         #61            //  (Ljava/lang/Object;)V
  #46 = MethodHandle       #6:#62         // invokestatic com/luo/demo/Application.lambda$main$0:(Ljava/lang/String;)V
  #47 = MethodType         #29            //  (Ljava/lang/String;)V
  #48 = NameAndType        #63:#64        // accept:()Ljava/util/function/Consumer;
  #49 = Class              #65            // java/util/ArrayList
  #50 = NameAndType        #66:#67        // forEach:(Ljava/util/function/Consumer;)V
  #51 = Class              #68            // java/lang/System
  #52 = NameAndType        #69:#70        // out:Ljava/io/PrintStream;
  #53 = Class              #71            // java/io/PrintStream
  #54 = NameAndType        #72:#29        // println:(Ljava/lang/String;)V
  #55 = Utf8               com/luo/demo/Application
  #56 = Utf8               java/lang/Object
  #57 = Utf8               cn/hutool/core/collection/CollectionUtil
  #58 = Utf8               newArrayList
  #59 = Utf8               ([Ljava/lang/Object;)Ljava/util/ArrayList;
  #60 = Methodref          #73.#74        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #61 = Utf8               (Ljava/lang/Object;)V
  #62 = Methodref          #11.#75        // com/luo/demo/Application.lambda$main$0:(Ljava/lang/String;)V
  #63 = Utf8               accept
  #64 = Utf8               ()Ljava/util/function/Consumer;
  #65 = Utf8               java/util/ArrayList
  #66 = Utf8               forEach
  #67 = Utf8               (Ljava/util/function/Consumer;)V
  #68 = Utf8               java/lang/System
  #69 = Utf8               out
  #70 = Utf8               Ljava/io/PrintStream;
  #71 = Utf8               java/io/PrintStream
  #72 = Utf8               println
  #73 = Class              #76            // java/lang/invoke/LambdaMetafactory
  #74 = NameAndType        #77:#81        // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #75 = NameAndType        #28:#29        // lambda$main$0:(Ljava/lang/String;)V
  #76 = Utf8               java/lang/invoke/LambdaMetafactory
  #77 = Utf8               metafactory
  #78 = Class              #83            // java/lang/invoke/MethodHandles$Lookup
  #79 = Utf8               Lookup
  #80 = Utf8               InnerClasses
  #81 = Utf8               (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #82 = Class              #84            // java/lang/invoke/MethodHandles
  #83 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #84 = Utf8               java/lang/invoke/MethodHandles
{
  public com.luo.demo.Application();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/luo/demo/Application;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: iconst_3
         1: anewarray     #2                  // class java/lang/String
         4: dup
         5: iconst_0
         6: ldc           #3                  // String I
         8: aastore
         9: dup
        10: iconst_1
        11: ldc           #4                  // String love
        13: aastore
        14: dup
        15: iconst_2
        16: ldc           #5                  // String you
        18: aastore
        19: invokestatic  #6                  // Method cn/hutool/core/collection/CollectionUtil.newArrayList:([Ljava/lang/Object;)Ljava/util/ArrayList;
        22: astore_1
        23: aload_1
        24: invokedynamic #7,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
        29: invokevirtual #8                  // Method java/util/ArrayList.forEach:(Ljava/util/function/Consumer;)V
        32: return
      LineNumberTable:
        line 15: 0
        line 16: 23
        line 19: 32
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      33     0  args   [Ljava/lang/String;
           23      10     1  list   Ljava/util/ArrayList;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
           23      10     1  list   Ljava/util/ArrayList<Ljava/lang/String;>;

  private static void lambda$main$0(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return
      LineNumberTable:
        line 17: 0
        line 18: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  item   Ljava/lang/String;
}

通过字节码可以看出,调用lambda方法时使用了invokedynamic,该字节码命令是为了支持动态语言特性而在Java7中新增的。Java的lambda表达式实现上也就借助于invokedynamic命令。

字节码中每一处含有invokeDynamic指令的位置都称为动态调用点,这条指令的第一个参数不再是代表方法调用符号引用的CONSTANT_Methodref_info常亮,而是变成为JDK7新加入的CONSTANT_InvokeDynamic_info常量,从这个新常量中可得到3项信息:引导方法(Bootstrap Method,此方法存放在新增的BootstrapMethods属性中)、方法类型和名称。引导方法是有固定的参数,并且返回值是java.lang.invoke.CallSite对象,这个代表真正要执行的目标方法调用。根据CONSTANT_InvokeDynamic_info常量中提供的信息,虚拟机可以找到并执行引导方法,从而获得一个CallSite对象,最终调用要执行的目标方法。

从上述mian方法的字节码可见,有一个invokeDynamic指令,他的参数为第7项常量(第二个值为0的参数HotSpot中用不到,占位符)。 invokedynamic #7, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;

常量池中第7项是#7 = InvokeDynamic #0:#48 // #0:accept:()Ljava/util/function/Consumer;,说明它是一项CONSTANT_InvokeDynamic_info常量,常量值中前面的#0表示引导方法取BootstrapMethods属性表的第0项,而后面的#48表示引用第48项类型为CONSTANAT_NameAndType_info的常量,从这个常量中可以获取方法名称和描述符,即accept方法。

代码语言:javascript
复制
BootstrapMethods:
  0: #44 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #45 (Ljava/lang/Object;)V
      #46 invokestatic com/luo/demo/Application.lambda$main$0:(Ljava/lang/String;)V
      #47 (Ljava/lang/String;)V

上图是在lambda代码中打断点时的调用栈信息,如果在这里的lambda中打印当前所属class,就是Application类,也印证了前面分析的lambda代码会生成一个private方法。

从调用栈的信息来看,是在accept方法中调用lambda对应的private方法(ambda$main$0)的,但是这里的accept方法是属于什么对象呢?从图中看是一串数字字符串,这里可以理解成一个Consumer接口的实现类即可,每个lambda表达式可以理解成在一个新的Consumer实现类中调用的即可。使用命令jmap -histo查看JVM进程类和对象信息可以看到这一行信息: 600: 1 16 com.luo.demo.Application$$Lambda$5/1615039080

参考资料:

1、https://blog.csdn.net/zxhoo/article/details/38387141

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 TopCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档