lambda表达式

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方法可以视为由四个基本内容所构成:

    • 名称
    • 签名(包含返回类型)
    • 定义它的类
    • 实现方法的字节码
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容