Java 8 Lambda實現原理分析

來源:互聯網
上載者:User

標籤:sign   article   nec   理論   rri   bst   slam   dma   進入   

轉載:http://blog.csdn.net/u012961566/article/details/78281654

 

為了支援函數式編程,Java 8引入了Lambda運算式,那麼在Java 8中到底是如何?Lambda運算式的呢? Lambda運算式經過編譯之後,到底會產生什麼東西呢? 在沒有深入分析前,讓我們先想一想,Java 8中每一個Lambda運算式必須有一個函數式介面與之對應,函數式介面與普通介面的區別,可以參考前面的內容,那麼你或許在想Lambda運算式是不是轉化成與之對應的函數式介面的一個實作類別呢,然後通過多態的方式調用子類的實現呢,如下面代碼是一個Lambda運算式的範例

@FunctionalInterfaceinterface Print<T> {    public void print(T x);}public class Lambda {       public static void PrintString(String s, Print<String> print) {        print.print(s);    }    public static void main(String[] args) {        PrintString("test", (x) -> System.out.println(x));    }}

按照上面的分析,理論上經過編譯器處理後,最終產生的程式碼應該如下面所示:

@FunctionalInterfaceinterface Print<T> {    public void print(T x);}class Lambda$$0 implements Print<String> {    @Override    public void print(String x) {        System.out.println(x);    }}public class Lambda {       public static void PrintString(String s,             Print<String> print) {        print.print(s);    }    public static void main(String[] args) {        PrintString("test", new Lambda$$0());    }}

再或者是一個內部類實現,代碼如下所示:

@FunctionalInterfaceinterface Print<T> {    public void print(T x);}public class Lambda {       final class Lambda$$0 implements Print<String> {        @Override        public void print(String x) {            System.out.println(x);        }    }      public static void PrintString(String s,             Print<String> print) {        print.print(s);    }     public static void main(String[] args) {        PrintString("test", new Lambda().new Lambda$$0());    }}

異或是這種匿名內部類實現,代碼如下所示:

@FunctionalInterfaceinterface Print<T> {    public void print(T x);}public class Lambda {       public static void PrintString(String s,             Print<String> print) {        print.print(s);    }    public static void main(String[] args) {        PrintString("test", new Print<String>() {            @Override            public void print(String x) {                System.out.println(x);            }        });    }}

上面的代碼,除了在代碼長度上長了點外,與用Lambda運算式實現的代碼運行結果是一樣的,那麼Java 8到底是用什麼方式實現的呢? 是不是上面三種實現方式中的一種呢,你也許覺的自已想的是對的,其實本來也就是對的,在Java 8中採用的是內部類來實現Lambda運算式

那麼Lambda運算式到底是如何?的呢?

為了探究Lambda運算式是如何?的,就得需要研究Lambda表過式最終轉化成的位元組碼檔案,這就需要jdk的bin目錄下的一個位元組碼查看工具及反編譯工具

javap -p Lambda.class

上面命令中的-p表示輸出所有類及成員,運行上面的命令後,得的結果如下所示:

Compiled from "Lambda.java"public class Lambda {  public Lambda();  public static void PrintString(java.lang.String, Print<java.lang.String>);  public static void main(java.lang.String[]);  private static void lambda$0(java.lang.String);}

由上面的代碼可以看出編譯器會根據Lambda運算式產生一個私人的靜態函數,注意,在這裡說的是產生,而不是等價

private static void lambda$0(java.lang.String);

為了驗證上面的轉化是否正確? 我們在代碼中定義一個lambda$0這個的函數,最終代碼如下所示: 

@FunctionalInterfaceinterface Print<T> {    public void print(T x);}public class Lambda {       public static void PrintString(String s,             Print<String> print) {        print.print(s);    }    private static void lambda$0(String s) {    }    public static void main(String[] args) {        PrintString("test", (x) -> System.out.println(x));    }}

上面的代碼在編譯時間不會報錯,但是運行時就會報錯,因為存在兩個lambda$0函數,如下所示,是運行時的錯誤

Exception in thread "main" java.lang.ClassFormatError: Duplicate method name&signature in class file Lambda    at java.lang.ClassLoader.defineClass1(Native Method)    at java.lang.ClassLoader.defineClass(ClassLoader.java:760)    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)    at java.security.AccessController.doPrivileged(Native Method)    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

通過javap對上述錯誤碼進行反編譯,反編譯之後輸出的類的成員如下所示

Compiled from "Lambda.java"public class Lambda {  public Lambda();  public static void PrintString(java.lang.String, Print<java.lang.String>);  private static void lambda$0(java.lang.String);  public static void main(java.lang.String[]);  private static void lambda$0(java.lang.String);}

會發現lambda$0出現了兩次,那麼在代碼啟動並執行時候,就不知道去調用哪個,因此就會拋錯。

有了上面的內容,可以知道的是Lambda運算式在Java 8中首先會產生一個私人的靜態函數,這個私人的靜態函數乾的就是Lambda運算式裡面的內容,因此上面的代碼初步可以轉化成如下所示的代碼

@FunctionalInterfaceinterface Print<T> {    public void print(T x);}public class Lambda {       public static void PrintString(String s, Print<String> print) {        print.print(s);    }        private static void lambda$0(String x) {        System.out.println(x);    }        public static void main(String[] args) {        PrintString("test", /**lambda expression**/);    }}

轉化成上面的形式之後,那麼如何?調用靜態lambda$0函數呢,在這裡可以在以下方法打上斷點,可以發現在有lambda運算式的地方,運行時會進入這個函數

 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();}

在這個函數中可以發現為Lambda運算式產生了一個內部類,為了驗證是否產生內部類,可以在運行時加上-Djdk.internal.lambda.dumpProxyClasses,加上這個參數後,運行時,會將產生的內部類class碼輸出到一個檔案中

final class Lambda$$Lambda$1 implements Print {  private Lambda$$Lambda$1();  public void print(java.lang.Object);}

如果運行javap -c -p 則結果如下

final class Lambda$$Lambda$1 implements Print {  private Lambda$$Lambda$1();    Code:       0: aload_0       1: invokespecial #10                 // Method java/lang/Object."<init>":()V       4: return  public void print(java.lang.Object);    Code:       0: aload_1       1: checkcast     #14                 // class java/lang/String       4: invokestatic  #20                 // Method Lambda.lambda$0:(Ljava/lang/String;)V       7: return}

通過上面的位元組碼指令可以發現實現上調用的是Lambda.lambda$0這個私人的靜態方法

因此最終的Lambda運算式等價於以下形式

@FunctionalInterfaceinterface Print<T> {    public void print(T x);}public class Lambda {       public static void PrintString(String s, Print<String> print) {        print.print(s);    }    private static void lambda$0(String x) {        System.out.println(x);    }    final class $Lambda$1 implements Print{        @Override        public void print(Object x) {            lambda$0((String)x);        }    }    public static void main(String[] args) {        PrintString("test", new Lambda().new $Lambda$1());    }}

Java 8 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.