美文网首页
lambda表达式

lambda表达式

作者: uranusleon | 来源:发表于2018-09-24 06:50 被阅读12次

    lambda表达式

    wiki

    学习目标

    • 代码层面理解怎么使用lambda表达式;
    • 编译层面理解lambda的机制;
    • 理解invokedynamic指令的机制;
    • bootstrap方法的参数具体是什么,以及怎么调试得到参数;
    • 疑问
      • metafactory方法的参数具体表示什么?
      • 字节码中关于boostraps方法的部分怎么理解?

    How:实现原理

    使用代码

    package CompilerTestPackage;
    
    import java.util.function.Consumer;
    
    public class LambdaTest {
        public static void main(String[] args)
        {
            Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.getName());
            greeter.accept(new Person("Lambda"));
        }
    }
    
    class Person{
        public String name;
    
        public Person(String name)
        {
            this.name = name;
        }
    
        public String getName()
        {
            return this.name;
        }
    }
    

    通过cfr反编译代码

    • 命令

      java -jar .\cfr_0_132.jar .\LambdaTest.class --decodelambdas false > D:\LambdTestCfr.txt

    package CompilerTestPackage;
    
    import CompilerTestPackage.Person;
    import java.io.PrintStream;
    import java.lang.invoke.LambdaMetafactory;
    import java.util.function.Consumer;
    
    public class LambdaTest {
        public static void main(String[] args) {
            Consumer<Person> greeter = (Consumer<Person>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(CompilerTestPackage.Person ), (LCompilerTestPackage/Person;)V)();
            greeter.accept(new Person("Lambda"));
        }
    
        private static /* synthetic */ void lambda$main$0(Person p) {
            System.out.println("Hello, " + p.getName());
        }
    }
    
    • Lambda表达式生成了一个lambdamain0()方法;
    • 调用LambdaMetafactory.metafactory()方法

    字节码层面

    Classfile /D:/test/LambdaTest.class
      Last modified 2018-8-5; size 1703 bytes
      MD5 checksum f7703a9885aa7896abfb5a773b708be2
      Compiled from "LambdaTest.java"
    public class CompilerTestPackage.LambdaTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #16.#38        // java/lang/Object."<init>":()V
       #2 = InvokeDynamic      #0:#44         // #0:accept:()Ljava/util/function/Consumer;
       #3 = Class              #45            // CompilerTestPackage/Person
       #4 = String             #46            // Lambda
       #5 = Methodref          #3.#47         // CompilerTestPackage/Person."<init>":(Ljava/lang/String;)V
       #6 = InterfaceMethodref #48.#49        // java/util/function/Consumer.accept:(Ljava/lang/Object;)V
       #7 = Fieldref           #50.#51        // java/lang/System.out:Ljava/io/PrintStream;
       #8 = Class              #52            // java/lang/StringBuilder
       #9 = Methodref          #8.#38         // java/lang/StringBuilder."<init>":()V
      #10 = String             #53            // Hello,
      #11 = Methodref          #8.#54         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #12 = Methodref          #3.#55         // CompilerTestPackage/Person.getName:()Ljava/lang/String;
      #13 = Methodref          #8.#56         // java/lang/StringBuilder.toString:()Ljava/lang/String;
      #14 = Methodref          #57.#58        // java/io/PrintStream.println:(Ljava/lang/String;)V
      #15 = Class              #59            // CompilerTestPackage/LambdaTest
      #16 = Class              #60            // java/lang/Object
      #17 = Utf8               <init>
      #18 = Utf8               ()V
      #19 = Utf8               Code
      #20 = Utf8               LineNumberTable
      #21 = Utf8               LocalVariableTable
      #22 = Utf8               this
      #23 = Utf8               LCompilerTestPackage/LambdaTest;
      #24 = Utf8               main
      #25 = Utf8               ([Ljava/lang/String;)V
      #26 = Utf8               args
      #27 = Utf8               [Ljava/lang/String;
      #28 = Utf8               greeter
      #29 = Utf8               Ljava/util/function/Consumer;
      #30 = Utf8               LocalVariableTypeTable
      #31 = Utf8               Ljava/util/function/Consumer<LCompilerTestPackage/Person;>;
      #32 = Utf8               lambda$main$0
      #33 = Utf8               (LCompilerTestPackage/Person;)V
      #34 = Utf8               p
      #35 = Utf8               LCompilerTestPackage/Person;
      #36 = Utf8               SourceFile
      #37 = Utf8               LambdaTest.java
      #38 = NameAndType        #17:#18        // "<init>":()V
      #39 = Utf8               BootstrapMethods
      #40 = MethodHandle       #6:#61         // 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;
      #41 = MethodType         #62            //  (Ljava/lang/Object;)V
      #42 = MethodHandle       #6:#63         // invokestatic CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
      #43 = MethodType         #33            //  (LCompilerTestPackage/Person;)V
      #44 = NameAndType        #64:#65        // accept:()Ljava/util/function/Consumer;
      #45 = Utf8               CompilerTestPackage/Person
      #46 = Utf8               Lambda
      #47 = NameAndType        #17:#66        // "<init>":(Ljava/lang/String;)V
      #48 = Class              #67            // java/util/function/Consumer
      #49 = NameAndType        #64:#62        // accept:(Ljava/lang/Object;)V
      #50 = Class              #68            // java/lang/System
      #51 = NameAndType        #69:#70        // out:Ljava/io/PrintStream;
      #52 = Utf8               java/lang/StringBuilder
      #53 = Utf8               Hello,
      #54 = NameAndType        #71:#72        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #55 = NameAndType        #73:#74        // getName:()Ljava/lang/String;
      #56 = NameAndType        #75:#74        // toString:()Ljava/lang/String;
      #57 = Class              #76            // java/io/PrintStream
      #58 = NameAndType        #77:#66        // println:(Ljava/lang/String;)V
      #59 = Utf8               CompilerTestPackage/LambdaTest
      #60 = Utf8               java/lang/Object
      #61 = Methodref          #78.#79        // 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;
      #62 = Utf8               (Ljava/lang/Object;)V
      #63 = Methodref          #15.#80        // CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
      #64 = Utf8               accept
      #65 = Utf8               ()Ljava/util/function/Consumer;
      #66 = Utf8               (Ljava/lang/String;)V
      #67 = Utf8               java/util/function/Consumer
      #68 = Utf8               java/lang/System
      #69 = Utf8               out
      #70 = Utf8               Ljava/io/PrintStream;
      #71 = Utf8               append
      #72 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #73 = Utf8               getName
      #74 = Utf8               ()Ljava/lang/String;
      #75 = Utf8               toString
      #76 = Utf8               java/io/PrintStream
      #77 = Utf8               println
      #78 = Class              #81            // java/lang/invoke/LambdaMetafactory
      #79 = NameAndType        #82:#86        // 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;
      #80 = NameAndType        #32:#33        // lambda$main$0:(LCompilerTestPackage/Person;)V
      #81 = Utf8               java/lang/invoke/LambdaMetafactory
      #82 = Utf8               metafactory
      #83 = Class              #88            // java/lang/invoke/MethodHandles$Lookup
      #84 = Utf8               Lookup
      #85 = Utf8               InnerClasses
      #86 = 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;
      #87 = Class              #89            // java/lang/invoke/MethodHandles
      #88 = Utf8               java/lang/invoke/MethodHandles$Lookup
      #89 = Utf8               java/lang/invoke/MethodHandles
    {
      public CompilerTestPackage.LambdaTest();
        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 5: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   LCompilerTestPackage/LambdaTest;
    
      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: invokedynamic #2,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
             5: astore_1
             6: aload_1
             7: new           #3                  // class CompilerTestPackage/Person
            10: dup
            11: ldc           #4                  // String Lambda
            13: invokespecial #5                  // Method CompilerTestPackage/Person."<init>":(Ljava/lang/String;)V
            16: invokeinterface #6,  2            // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
            21: return
          LineNumberTable:
            line 8: 0
            line 9: 6
            line 10: 21
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      22     0  args   [Ljava/lang/String;
                6      16     1 greeter   Ljava/util/function/Consumer;
          LocalVariableTypeTable:
            Start  Length  Slot  Name   Signature
                6      16     1 greeter   Ljava/util/function/Consumer<LCompilerTestPackage/Person;>;
    }
    SourceFile: "LambdaTest.java"
    InnerClasses:
         public static final #84= #83 of #87; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
    BootstrapMethods:
      0: #40 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:
          #41 (Ljava/lang/Object;)V
          #42 invokestatic CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
          #43 (LCompilerTestPackage/Person;)V
    
    • 从字节码中可以看出,lambda表达式是通过invokedynamic实现的;

    lambda表达式的invokedynamic指令

    • invokedynamic指令有四个操作数,格式为

      invokedynamic indexbyte1 indexbyte2 0 0

      • 前两个操作数构成一个索引,指向常量池,关联CONSTANT_InvokeDynamic_info结构;
      • 后两个操作数保留,必须为0;
    • CONSTANT_InvokeDynamic_info 结构定义

      CONSTANT_InvokeDynamic_info {
       u1 tag;
       u2 bootstrap_method_attr_index; // 指向bootstrap_methods的一个有效索引值,其结构在属性表的 bootstrap method 结构中
       u2 name_and_type_index;
      }
      
    • BootstrapMethods属性结构

      BootstrapMethods_attribute {
          u2 attribute_name_index;
          u4 attribute_length;
          u2 num_bootstrap_methods;
          {   u2 bootstrap_method_ref;
              u2 num_bootstrap_arguments;
              u2 bootstrap_arguments[num_bootstrap_arguments];
          } bootstrap_methods[num_bootstrap_methods];
      }
      
    • 指令调用图

    lambda的机制总结

    编译时

    • lambda表达式生产一个方法,方法实现了表达式的代码逻辑;
    • 编译生成invokedynamic指令,调用bootstrap方法,由java.lang.invoke.LambdaMetafactory.metafactory()方法实现;

    运行时

    • invokedynamic指令调用metafactory方法,返回一个callsite,此callsite返回目标类型的一个匿名实现类MethodHandles.Lookup caller 的内部类),此类关联编译时产生的方法;
    • lambda表达式调用时会调用匿名实现类关联的方法。

    LambdaMetafactory.metafactory()方法

    public static CallSite metafactory(MethodHandles.Lookup caller,
                                           String invokedName,
                                           MethodType invokedType,
                                           MethodType samMethodType,
                                           MethodHandle implMethod,
                                           MethodType instantiatedMethodType)
                throws LambdaConversionException {
            AbstractValidatingLambdaMetafactory mf;
            mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                                 invokedName, samMethodType,
                                                 implMethod, instantiatedMethodType,
                                                 false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
            mf.validateMetafactoryArgs();
            return mf.buildCallSite();
        }
    
    metafactory方法的参数
    • caller: 由JVM提供的lookup context
    • invokedName: JVM提供的NameAndType
    • invokedType: JVM提供的期望的CallSite类型
    • samMethodType: 函数式接口定义的方法的签名
    • implMethod: 编译时产生的那个实现方法
    • instantiatedMethodType: 强制的方法签名和返回类型, 一般和samMethodType相同或者是它的一个特例

    查看匿名实现类

    IDEA配置JVM参数
    • Run-->Edit Configurations
    • 在VM Options内输入-Djdk.internal.lambda.dumpProxyClasses=<path_to_your_dump_directory>
    • 匿名类查看
    package CompilerTestPackage;
    
    import CompilerTestPackage.LambdaTest;
    import CompilerTestPackage.Person;
    import java.lang.invoke.LambdaForm;
    import java.util.function.Consumer;
    
    final class LambdaTest$$Lambda$1
    implements Consumer {
        private LambdaTest$$Lambda$1() {
        }
    
        @LambdaForm.Hidden
        public void accept(Object object) {
            LambdaTest.lambda$main$0((Person)((Person)object));// 导入lambda$main$0方法
        }
    }
    

    invokedynamic指令学习

    • 理念

      要让invokedynamic正常运行,一个核心的概念就是方法句柄(method handle)。它代表了一个可以从invokedynamic调用点进行调用的方法。这里的基本理念就是每个invokedynamic指令都会与一个特定的方法关联(也就是引导方法或BSM)。当解释器(interpreter)遇到invokedynamic指令的时候,BSM会被调用。它会返回一个对象(包含了一个方法句柄),这个对象表明了调用点要实际执行哪个方法。

    • 一个Java方法可以视为由四个基本内容所构成:

      • 名称
      • 签名(包含返回类型)
      • 定义它的类
      • 实现方法的字节码

    相关文章

      网友评论

          本文标题:lambda表达式

          本文链接:https://www.haomeiwen.com/subject/tvrcoftx.html