快速學習Lambda表達式

先看以下的一個示例:

@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 MainKaTeX parse error: Can't use function '$' in math mode at position 7: Lambda$̲1.class反編譯MainLambda$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$$Lambda1Printoutput調Mainlambda1實現了Print接口,並且output方法中,調用了Main類中的私有靜態方法lambdamain$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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章