Java8 Lambda運算式深入學習(4) -- Java8實現方式

來源:互聯網
上載者:User

標籤:java   lambda   

前幾篇文章討論了函數式介面和Lambda運算式文法、invokedynamic指令,以及Groovy2如何利用indy指令。本篇文章在前面幾篇的基礎之上,簡要介紹Java8底層是如何?Lambda運算式的。

範例程式碼

本文將以下面的代碼為例展開討論:

import java.util.Arrays;import java.util.List;public class LambdaImplTest {        public static void main(String[] args) {        m1(Arrays.asList(args));    }        public static void m1(List<String> list) {        list.sort((a, b) -> a.length() - b.length());    }    }

插入invokedynamic指令

直接利用匿名類來實現Lambda運算式肯定也是可行的,這樣,Lambda運算式就只是Java編譯器的文法糖而已。但是Java8並沒有這樣做,而是使用了更複雜(也更靈活)的方式:利用indy指令。顯然,這種方式需要編譯器和JVM一同配合來實現。編譯器會在每個Lambda運算式出現的地方插入一條indy指令,同時還必須在class檔案裡產生相應的CONSTANT_InvokeDynamic_info常量池項和BootstrapMethods屬性等資訊。這些資訊將這條indy指令的bootstrap方法指向LambdaMetafactory.metafactory(...)方法。

插入lambda$x&y方法

用javac編譯LambdaImplTest.java,然後用javap -v -p反編譯.class檔案,可以看到,編譯器產生了一個lambda$m1$0方法,並且將Lambda運算式的內容搬到了裡面:

編譯器會按照一定的規則來給Lambda運算式產生方法,以保證這些方法不會重名。如果把位元組碼還原成Java代碼的話,LambdaImplTest看起來會像下面這樣:

public class LambdaImplTest {        public static void main(String[] args) {        m1(Arrays.asList(args));    }        public static void m1(List<String> list) {        list.sort(/*lambda*/);    }        private static int lambda$m1$0(String a, String b) {        return a.length() - b.length();    }    }

LambdaMetafactory.metafactory(...)方法

當JVM第一次執行到這條indy指令的時候,它會找到這條指令相應的bootstrap方法,然後調用該方法,得到一個CallSite。下面是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();    }
從代碼可以猜到,Java8內部也是以內部類的方式來實現Lambda運算式的。而InnerClassLambdaMetafactory的buildCallSite()方法證明了這一點,buildCallSite()方法太長,這裡就不貼代碼了,總之它會調用一個叫做spinInnerClass()的方法,正是這個方法使用位元組碼工具在記憶體中產生了一個類。

觀察spinInnerClass()產生的類

如果我們在啟動JVM的時候設定系統屬性"jdk.internal.lambda.dumpProxyClasses"的話,spinnerClass()方法就會將產生的類儲存到檔案中,以方便調試。如果我們用下面的命令運行LambdaImplTest,就能在“目前的目錄”看到這個類:

java -Djdk.internal.lambda.dumpProxyClasses LambdaImplTest
LambdaImplTest$$Lambda$1.class
同樣可以使用javap命令來反編譯這個class檔案,下面是反編譯結果(我自己把javap結果轉成了java檔案):

final class LambdaImplTest$$Lambda$1 implements java.util.Comparator {        private LambdaImplTest$$Lambda$1() {    }    public int compare(Object a, Object b) {        return LambdaImplTest.lambda$m1$0:((String) a, (String) b);    }}
可見這個內部類實現了Comparator介面,compare()方法只是調用lambda$m1$0()方法而已。繼續分析buildCallSite()方法可知,JVM接著執行個體化了這個內部類的一個執行個體,然後建立了一個ConstantCallSite,它的目標MethodHandle指向內部類執行個體的compare()方法。

文字描述很難說清楚問題,下面我畫了一張:


總結

上面分析了最簡單的Lambda運算式的Java內部實現,如果Lambda運算式捕獲了外部參數的話,情況會稍微複雜一點。不過結論仍然不變:Java8底層也是以內部類的方式來實現Lambda運算式的。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.