【JAVA】Lambda執行原理

在我的想法裏,Lambda長得很奇怪,虛擬機真的認識這玩意嗎?還是說,Lambda經過編譯後,脫掉了僞裝的衣服,變成了大家熟知的方法?

對Lambda不熟悉的同學,可以先看我的另外兩篇文章。


先看以下的一個示例:

//使用註解@FunctionalInterface來聲明這是一個函數式接口
@FunctionalInterface
interface Print {
    void output(String str);
}

public class Main {

    private static void handle(String str, Print p) {
        p.output(str);
    }

    public static void main(String[] args) {
        handle("abc", str -> System.out.println(str));
    }
}

運行後,顯然輸出abc。

那麼,這段代碼被編譯成什麼樣子了呢,我們使用javap -p Main.class查看編譯後的類成員信息(-p顯示所有的類和成員)

public class com.yang.testLambda.Main {
  public com.yang.testLambda.Main();
  private static void handle(java.lang.String, com.yang.testLambda.Print);
  public static void main(java.lang.String[]);
  private static void lambda$main$0(java.lang.String);
}

可以看到,多出來一個私有靜態方法lambda$main$0

那這個靜態方法,裏面的內容又是什麼呢?

繼續使用javap -c -p Main.class(-c對代碼進行反彙編)看看

public class com.yang.testLambda.Main {
  public com.yang.testLambda.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  private static void handle(java.lang.String, com.yang.testLambda.Print);
    Code:
       0: aload_1
       1: aload_0
       2: invokeinterface #2,  2            // InterfaceMethod com/yang/testLambda/Print.output:(Ljava/lang/String;)V
       7: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #3                  // String abc
       2: invokedynamic #4,  0              // InvokeDynamic #0:output:()Lcom/yang/testLambda/Print;
       7: invokestatic  #5                  // Method handle:(Ljava/lang/String;Lcom/yang/testLambda/Print;)V
      10: return

  private static void lambda$main$0(java.lang.String);
    Code:
       0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       7: return
}

只看lambda$main$0方法,發現裏面是

new PrintStream("abc").println();

也就是

System.out.println("abc")

說明,生成的私有靜態方法裏面的內容就是lambda表達式裏面的主要內容。

那麼,這個私有靜態方法,是何時何地被誰調用的呢?

現在需要使用javac Main.java編譯成Main.class文件,之後使用java -Djdk.internal.lambda.dumpProxyClasses Main來運行,並會將運行過程中產生的內部類輸出出來。

運行第一個命令後,會產生Main.class和Print.class文件

運行第二個命令後,會額外產生Main$$Lambda$1.class文件

使用javap -c -p Main$$Lambda$1.class反編譯Main$$Lambda$1.class文件,會得到

final class Main$$Lambda$1 implements Print {
  private Main$$Lambda$1();
    Code:
       0: aload_0
       1: invokespecial #10                 // Method java/lang/Object."<init>":()V
       4: return

  public void output(java.lang.String);
    Code:
       0: aload_1
       1: invokestatic  #18                 // Method Main.lambda$main$0:(Ljava/lang/String;)V
       4: return
}

發現Main$$Lambda$1實現了Print接口,並且output方法中,調用了Main類中的私有靜態方法lambda$main$0

那麼,該內部類又是何時何地被誰調用的呢?

而在一開始我們使用javap -c-p Main.class時,其中主方法是:

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #3                  // String abc
       2: invokedynamic #4,  0              // InvokeDynamic #0:output:()Lcom/yang/testLambda/Print;
       7: invokestatic  #5                  // Method handle:(Ljava/lang/String;Lcom/yang/testLambda/Print;)V
      10: return

可以看得出這邊使用了invokedynamic調用了函數式接口,可以粗略的認爲這裏實例化了Print的實現類(其實具體的邏輯太挺複雜,這裏直接簡化了),就是內部類Main$$Lambda$1,然後調用靜態方法handle,這個方法接收一個字符串和Print實現類實例。

那麼,一開始的lambda表達式,可以改寫成這樣的形式:

interface Print {
    void output(String str);
}

public class Main {

    private static void handle(String str, Print p) {
        p.output(str);
    }

    //編譯後生成的私有靜態方法,方法內容就是lambda裏的內容
    private static void lambda$main$0(String str) {
        System.out.println(str);
    }

    //運行時生成的內部類,實現函數式接口,實現方法中調用私有靜態方法
    final class Main$$Lambda$1 implements Print {

        @Override
        public void output(String str) {
            lambda$main$0(str);
        }
    }

    public static void main(String[] args) {
        Print print = new Main().new Main$$Lambda$1();
        handle("abc", print);
    }
}

到這裏,lambda表達式的執行原理,已經粗淺的解釋完畢了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章