lambda表達(dá)式

lambda表達(dá)式

wiki

學(xué)習(xí)目標(biāo)

  • 代碼層面理解怎么使用lambda表達(dá)式贴铜;
  • 編譯層面理解lambda的機(jī)制屑柔;
  • 理解invokedynamic指令的機(jī)制岁疼;
  • bootstrap方法的參數(shù)具體是什么,以及怎么調(diào)試得到參數(shù)踢代;
  • 疑問
    • metafactory方法的參數(shù)具體表示什么?
    • 字節(jié)碼中關(guān)于boostraps方法的部分怎么理解?

How:實(shí)現(xiàn)原理

使用代碼

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表達(dá)式生成了一個(gè)lambdamain0()方法岳守;
  • 調(diào)用LambdaMetafactory.metafactory()方法

字節(jié)碼層面

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
  • 從字節(jié)碼中可以看出,lambda表達(dá)式是通過invokedynamic實(shí)現(xiàn)的碌冶;

lambda表達(dá)式的invokedynamic指令

  • invokedynamic指令有四個(gè)操作數(shù)湿痢,格式為

    invokedynamic indexbyte1 indexbyte2 0 0

    • 前兩個(gè)操作數(shù)構(gòu)成一個(gè)索引,指向常量池扑庞,關(guān)聯(lián)CONSTANT_InvokeDynamic_info結(jié)構(gòu)譬重;
    • 后兩個(gè)操作數(shù)保留,必須為0罐氨;
  • CONSTANT_InvokeDynamic_info 結(jié)構(gòu)定義

    CONSTANT_InvokeDynamic_info {
     u1 tag;
     u2 bootstrap_method_attr_index; // 指向bootstrap_methods的一個(gè)有效索引值臀规,其結(jié)構(gòu)在屬性表的 bootstrap method 結(jié)構(gòu)中
     u2 name_and_type_index;
    }
    
  • BootstrapMethods屬性結(jié)構(gòu)

    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];
    }
    
  • 指令調(diào)用圖

lambda的機(jī)制總結(jié)

編譯時(shí)

  • lambda表達(dá)式生產(chǎn)一個(gè)方法,方法實(shí)現(xiàn)了表達(dá)式的代碼邏輯栅隐;
  • 編譯生成invokedynamic指令塔嬉,調(diào)用bootstrap方法,由java.lang.invoke.LambdaMetafactory.metafactory()方法實(shí)現(xiàn)租悄;

運(yùn)行時(shí)

  • invokedynamic指令調(diào)用metafactory方法谨究,返回一個(gè)callsite,此callsite返回目標(biāo)類型的一個(gè)匿名實(shí)現(xiàn)類MethodHandles.Lookup caller 的內(nèi)部類)泣棋,此類關(guān)聯(lián)編譯時(shí)產(chǎn)生的方法胶哲;
  • lambda表達(dá)式調(diào)用時(shí)會(huì)調(diào)用匿名實(shí)現(xiàn)類關(guān)聯(lián)的方法。

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方法的參數(shù)
  • caller: 由JVM提供的lookup context
  • invokedName: JVM提供的NameAndType
  • invokedType: JVM提供的期望的CallSite類型
  • samMethodType: 函數(shù)式接口定義的方法的簽名
  • implMethod: 編譯時(shí)產(chǎn)生的那個(gè)實(shí)現(xiàn)方法
  • instantiatedMethodType: 強(qiáng)制的方法簽名和返回類型潭辈, 一般和samMethodType相同或者是它的一個(gè)特例

查看匿名實(shí)現(xiàn)類

IDEA配置JVM參數(shù)
  • Run-->Edit Configurations
  • 在VM Options內(nèi)輸入-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));// 導(dǎo)入lambda$main$0方法
    }
}

invokedynamic指令學(xué)習(xí)

  • 理念

    要讓invokedynamic正常運(yùn)行鸯屿,一個(gè)核心的概念就是方法句柄(method handle)。它代表了一個(gè)可以從invokedynamic調(diào)用點(diǎn)進(jìn)行調(diào)用的方法萎胰。這里的基本理念就是每個(gè)invokedynamic指令都會(huì)與一個(gè)特定的方法關(guān)聯(lián)(也就是引導(dǎo)方法或BSM)碾盟。當(dāng)解釋器(interpreter)遇到invokedynamic指令的時(shí)候,BSM會(huì)被調(diào)用技竟。它會(huì)返回一個(gè)對(duì)象(包含了一個(gè)方法句柄)冰肴,這個(gè)對(duì)象表明了調(diào)用點(diǎn)要實(shí)際執(zhí)行哪個(gè)方法。

  • 一個(gè)Java方法可以視為由四個(gè)基本內(nèi)容所構(gòu)成:

    • 名稱
    • 簽名(包含返回類型)
    • 定義它的類
    • 實(shí)現(xiàn)方法的字節(jié)碼
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市熙尉,隨后出現(xiàn)的幾起案子联逻,更是在濱河造成了極大的恐慌,老刑警劉巖检痰,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件包归,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡铅歼,警方通過查閱死者的電腦和手機(jī)公壤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椎椰,“玉大人厦幅,你說我怎么就攤上這事】” “怎么了确憨?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瓤的。 經(jīng)常有香客問我休弃,道長(zhǎng),這世上最難降的妖魔是什么圈膏? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任塔猾,我火速辦了婚禮,結(jié)果婚禮上本辐,老公的妹妹穿的比我還像新娘桥帆。我一直安慰自己,他們只是感情好慎皱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布老虫。 她就那樣靜靜地躺著,像睡著了一般茫多。 火紅的嫁衣襯著肌膚如雪祈匙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天天揖,我揣著相機(jī)與錄音夺欲,去河邊找鬼。 笑死今膊,一個(gè)胖子當(dāng)著我的面吹牛些阅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斑唬,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼市埋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼黎泣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缤谎,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤抒倚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后坷澡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體托呕,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年频敛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了项郊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡姻政,死狀恐怖呆抑,靈堂內(nèi)的尸體忽然破棺而出岂嗓,到底是詐尸還是另有隱情汁展,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布厌殉,位于F島的核電站食绿,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏公罕。R本人自食惡果不足惜器紧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望楼眷。 院中可真熱鬧铲汪,春花似錦、人聲如沸罐柳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽张吉。三九已至齿梁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肮蛹,已是汗流浹背勺择。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伦忠,地道東北人省核。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像昆码,于是被迫代替她去往敵國和親气忠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邓深,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容