由淺入深學(xué)習(xí)java8的Lambda原理

java8lambda由淺入深上陕,通過一個簡單例子覆劈,逐步深入了解lambda實現(xiàn)原理荔睹。

一個簡單的java8中l(wèi)ambda例子

先看一個簡單的使用lambda的例子揖盘,我們從這個例子開始逐步探索java8中l(wèi)ambda是如何實現(xiàn)的楷力。

/**
 * Created by qiyan on 2017/4/16.
 */
public class LambdaTest {

    public static void main(String[] args) {
        Func add = (x, y) -> x + y;
        System.out.println(add.exec(1, 2));
    }
}


@FunctionalInterface
interface Func {
    int exec(int x, int y);
}

上面源碼編譯完成后執(zhí)行 javap -p -v -c LambdaTest 查看反編譯結(jié)果:

Classfile /Users/qiyan/src/test/src/main/java/LambdaTest.class
  Last modified 2017-4-16; size 969 bytes
  MD5 checksum 0a1db458a90b20fbfae645b576725fd4
  Compiled from "LambdaTest.java"
public class LambdaTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#18         // java/lang/Object."<init>":()V
   #2 = InvokeDynamic      #0:#23         // #0:exec:()LFunc;
   #3 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = InterfaceMethodref #26.#27        // Func.exec:(II)I
   #5 = Methodref          #28.#29        // java/io/PrintStream.println:(I)V
   #6 = Class              #30            // LambdaTest
   #7 = Class              #31            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               lambda$main$0
  #15 = Utf8               (II)I
  #16 = Utf8               SourceFile
  #17 = Utf8               LambdaTest.java
  #18 = NameAndType        #8:#9          // "<init>":()V
  #19 = Utf8               BootstrapMethods
  #20 = MethodHandle       #6:#32         // 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;
  #21 = MethodType         #15            //  (II)I
  #22 = MethodHandle       #6:#33         // invokestatic LambdaTest.lambda$main$0:(II)I
  #23 = NameAndType        #34:#35        // exec:()LFunc;
  #24 = Class              #36            // java/lang/System
  #25 = NameAndType        #37:#38        // out:Ljava/io/PrintStream;
  #26 = Class              #39            // Func
  #27 = NameAndType        #34:#15        // exec:(II)I
  #28 = Class              #40            // java/io/PrintStream
  #29 = NameAndType        #41:#42        // println:(I)V
  #30 = Utf8               LambdaTest
  #31 = Utf8               java/lang/Object
  #32 = Methodref          #43.#44        // 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;
  #33 = Methodref          #6.#45         // LambdaTest.lambda$main$0:(II)I
  #34 = Utf8               exec
  #35 = Utf8               ()LFunc;
  #36 = Utf8               java/lang/System
  #37 = Utf8               out
  #38 = Utf8               Ljava/io/PrintStream;
  #39 = Utf8               Func
  #40 = Utf8               java/io/PrintStream
  #41 = Utf8               println
  #42 = Utf8               (I)V
  #43 = Class              #46            // java/lang/invoke/LambdaMetafactory
  #44 = NameAndType        #47:#51        // 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 = NameAndType        #14:#15        // lambda$main$0:(II)I
  #46 = Utf8               java/lang/invoke/LambdaMetafactory
  #47 = Utf8               metafactory
  #48 = Class              #53            // java/lang/invoke/MethodHandles$Lookup
  #49 = Utf8               Lookup
  #50 = Utf8               InnerClasses
  #51 = 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;
  #52 = Class              #54            // java/lang/invoke/MethodHandles
  #53 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #54 = Utf8               java/lang/invoke/MethodHandles
{
  public 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 4: 0

  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:exec:()LFunc;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: iconst_1
        11: iconst_2
        12: invokeinterface #4,  3            // InterfaceMethod Func.exec:(II)I
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 7: 0
        line 8: 6
        line 9: 20

  private static int lambda$main$0(int, int);
    descriptor: (II)I
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 7: 0
}
SourceFile: "LambdaTest.java"
InnerClasses:
     public static final #49= #48 of #52; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #20 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:
      #21 (II)I
      #22 invokestatic LambdaTest.lambda$main$0:(II)I
      #21 (II)I

根據(jù)反編譯結(jié)果喊式,不難看出lambda表達式:(x, y) -> x + y 被編譯成了一個方法:lambdamain0

private static int lambda$main$0(int, int);
    descriptor: (II)I
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: iadd
         3: ireturn
      LineNumberTable:
        line 7: 0

翻譯成java代碼:

private static int lambda$main$0(int x, int y){
    return x + y;
}

再看看main方法字節(jié)碼:

  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:exec:()LFunc;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: iconst_1
        11: iconst_2
        12: invokeinterface #4,  3            // InterfaceMethod Func.exec:(II)I
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        20: return
      LineNumberTable:
        line 7: 0
        line 8: 6
        line 9: 20

執(zhí)行main步驟:

0:通過invokedynamic指令生成調(diào)用對象孵户;
5:存入本地變量表;
6:加載java.lang.System.out靜態(tài)方法岔留;
9:將lambda表達式生成的對象加載入執(zhí)行棧夏哭;
10:將int類型1加載入執(zhí)行棧;
11:將int類型2加載入執(zhí)行棧献联;
12:執(zhí)行l(wèi)ambda表達式生成的對象的exec方法方庭;
17:輸出執(zhí)行結(jié)果;

lambda的要點就在invokedynamic這個指令了酱固,通過invokedynamic指令生成目標對象械念,接下來我們了解一下invokedynamic指令。

invokedynamic指令

下面重點看看invokedynamic指令运悲,首先我們來看看常量池中出現(xiàn)的的InvokeDynamic類型:

官方文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.10

CONSTANT_InvokeDynamic_info {
    u1 tag;//InvokeDynamic類型標記18
    u2 bootstrap_method_attr_index; //BootstrapMethods_attribute中的坐標
    u2 name_and_type_index; //名字&類型常量池坐標
}

接下來看看BootstrapMethods_attribute龄减,InvokeDynamic中的bootstrap_method_attr_index就是指向其中bootstrap_methods的下標:
官方文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.21

BootstrapMethods_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 num_bootstrap_methods;
    {   u2 bootstrap_method_ref;//方法引用
        u2 num_bootstrap_arguments;//參數(shù)數(shù)量
        u2 bootstrap_arguments[num_bootstrap_arguments];//參數(shù)
    } bootstrap_methods[num_bootstrap_methods];
}

繼續(xù)bootstrap_methods中的bootstrap_method_ref:

官方文檔:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.8

CONSTANT_MethodHandle_info {
    u1 tag;//MethodHandle類型標記15
    u1 reference_kind;//方法引用類型getfield/getstatic/putfield/putstatic/invokevirtual/invokestatic/invokespecial/new/invokeinterface,此例中使用的invokestatic班眯。
    u2 reference_index;//引用類型希停,根據(jù)reference_kind確認,例如本例中kind為方法調(diào)用署隘,所以index為Methodref宠能,細節(jié)可以查看官方文檔。
}

回歸lambda例子

對invokedynamic學(xué)習(xí)總結(jié)一下:
invokedynamic指令通過找到BootstrapMethods中的方法磁餐,生成動態(tài)調(diào)用點违崇,對于本例,我們對照BootstrapMethods中的bootstrap_methods分析實現(xiàn):

BootstrapMethods:
  0: #20 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:
      #21 (II)I
      #22 invokestatic LambdaTest.lambda$main$0:(II)I
      #21 (II)I

按照上面指令诊霹,可以看出羞延,invokedynamic指令通過java.lang.invoke.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();
}

查看相關(guān)代碼可以看出脾还,metafactory就是核心方法伴箩,該方法通過InnerClassLambdaMetafactory類生成對象,供后續(xù)調(diào)用鄙漏,在InnerClassLambdaMetafactory源碼中可以看到嗤谚,有提供開關(guān)是否dump生成的class文件。

private static final ProxyClassesDumper dumper;

static {
    final String key = "jdk.internal.lambda.dumpProxyClasses";
    String path = AccessController.doPrivileged(new GetPropertyAction(key), null,new PropertyPermission(key , "read"));
    dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path);
}

//dump邏輯
if (dumper != null) {
  AccessController.doPrivileged(new PrivilegedAction<Void>() {
    @Override
    public Void run() {
    dumper.dumpClass(lambdaClassName, classBytes);
    return null;
    }
  }, null,
    new FilePermission("<<ALL FILES>>", "read, write"),
    // createDirectories may need it
    new PropertyPermission("user.dir", "read"));
}

執(zhí)行下面命令生成中間對象java -Djdk.internal.lambda.dumpProxyClasses LambdaTest

Classfile /Users/qiyan/src/test/src/main/java/LambdaTest$$Lambda$1.class
  Last modified 2017-4-17; size 236 bytes
  MD5 checksum 983fa2b5e7d29c46d6f885925909b83e
final class LambdaTest$$Lambda$1 implements Func
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
   #1 = Utf8               LambdaTest$$Lambda$1
   #2 = Class              #1             // LambdaTest$$Lambda$1
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Func
   #6 = Class              #5             // Func
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = NameAndType        #7:#8          // "<init>":()V
  #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
  #11 = Utf8               exec
  #12 = Utf8               (II)I
  #13 = Utf8               LambdaTest
  #14 = Class              #13            // LambdaTest
  #15 = Utf8               lambda$main$0
  #16 = NameAndType        #15:#12        // lambda$main$0:(II)I
  #17 = Methodref          #14.#16        // LambdaTest.lambda$main$0:(II)I
  #18 = Utf8               Code
{
  private LambdaTest$$Lambda$1();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return

  public int exec(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: invokestatic  #17                 // Method LambdaTest.lambda$main$0:(II)I
         5: ireturn
}

第一個例子學(xué)習(xí)分析到此結(jié)束怔蚌,下面是根據(jù)原理巩步,翻譯的等價的最終執(zhí)行代碼。

public class LambdaTest {

    public static void main(String[] args) {
        Func add = new LambdaTest$$Lambda$1();
        System.out.println(add.exec(1, 2));
    }

    private static int lambda$main$0(int x, int y) {
        return x + y;
    }

    static final class LambdaTest$$Lambda$1 implements Func {
        private LambdaTest$$Lambda$1() {
        }

        public int exec(int x, int y) {
            return LambdaTest.lambda$main$0(x, y);
        }
    }
}


@FunctionalInterface
interface Func {
    int exec(int x, int y);
}

后續(xù)有時間繼續(xù)補充變量作用域相關(guān)實現(xiàn)原理...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末媚创,一起剝皮案震驚了整個濱河市渗钉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钞钙,老刑警劉巖鳄橘,帶你破解...
    沈念sama閱讀 216,692評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芒炼,居然都是意外死亡瘫怜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評論 3 392
  • 文/潘曉璐 我一進店門本刽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鲸湃,“玉大人,你說我怎么就攤上這事子寓“堤簦” “怎么了?”我有些...
    開封第一講書人閱讀 162,995評論 0 353
  • 文/不壞的土叔 我叫張陵斜友,是天一觀的道長炸裆。 經(jīng)常有香客問我,道長鲜屏,這世上最難降的妖魔是什么烹看? 我笑而不...
    開封第一講書人閱讀 58,223評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮洛史,結(jié)果婚禮上惯殊,老公的妹妹穿的比我還像新娘。我一直安慰自己也殖,他們只是感情好土思,可當(dāng)我...
    茶點故事閱讀 67,245評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忆嗜,像睡著了一般浪漠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霎褐,一...
    開封第一講書人閱讀 51,208評論 1 299
  • 那天址愿,我揣著相機與錄音,去河邊找鬼冻璃。 笑死响谓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的省艳。 我是一名探鬼主播娘纷,決...
    沈念sama閱讀 40,091評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跋炕!你這毒婦竟也來了赖晶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,929評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遏插,沒想到半個月后捂贿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,346評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡胳嘲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,570評論 2 333
  • 正文 我和宋清朗相戀三年厂僧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片了牛。...
    茶點故事閱讀 39,739評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡颜屠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹰祸,到底是詐尸還是另有隱情甫窟,我是刑警寧澤,帶...
    沈念sama閱讀 35,437評論 5 344
  • 正文 年R本政府宣布蛙婴,位于F島的核電站粗井,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏敬锐。R本人自食惡果不足惜背传,卻給世界環(huán)境...
    茶點故事閱讀 41,037評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望台夺。 院中可真熱鬧径玖,春花似錦、人聲如沸颤介。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,677評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滚朵。三九已至冤灾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辕近,已是汗流浹背韵吨。 一陣腳步聲響...
    開封第一講書人閱讀 32,833評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留移宅,地道東北人归粉。 一個月前我還...
    沈念sama閱讀 47,760評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像漏峰,于是被迫代替她去往敵國和親糠悼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,647評論 2 354

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理浅乔,服務(wù)發(fā)現(xiàn)倔喂,斷路器,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 今日體驗,再一次感受到語言的強大席噩,但是反思懺悔班缰,真正強大的是需要嗎?錯了班挖,需要的背后一定是需要系統(tǒng)的支撐鲁捏,體驗二芯砸,...
    王海博閱讀 180評論 0 0
  • 隨著年齡的增長假丧,很多東西不再奢望誰買給自己双揪。因為我也可以。 很小的時候包帚,羨慕別的孩子有可愛的洋娃娃渔期,纏著老爸要了很...
    梅園遺珠閱讀 542評論 0 0
  • 文/巴山雨(簡書作者)轉(zhuǎn)載請聯(lián)系作者授權(quán)。如有雷同渴邦,你抄我的疯趟,侵權(quán)必究。 巴山雨作品全目錄更多精彩谋梭,快戳簡書連載風(fēng)...
    巴山雨閱讀 335評論 0 2