通過位元組碼展示Java8 Lambda的實現

来源:https://www.cnblogs.com/bigshark/archive/2019/07/29/11257395.html
-Advertisement-
Play Games

Java8 增加了 Lambda 表達式,很大程度使代碼變的更加簡潔緊湊了,那麼 Java8 是如何實現 Lambda 表達式的呢? 直接看一個簡單的創建線程的例子。 執行 編譯生成文件 ,然後用 命令來分析這個class文件。 執行 顯示所有類和成員。 由上面的代碼可以看出編譯器根據 Lambda ...


Java8 增加了 Lambda 表達式,很大程度使代碼變的更加簡潔緊湊了,那麼 Java8 是如何實現 Lambda 表達式的呢?

直接看一個簡單的創建線程的例子。

public class TestLambda {
    public static void main(String[] args) {
        new Thread(() -> System.out.print("thread"));
    }
}

執行javac TestLambda.java編譯生成文件TestLambda.class,然後用javap命令來分析這個class文件。
執行javap -p TestLambda顯示所有類和成員。

// javap -p TestLambda
Compiled from "TestLambda.java"
public class TestLambda {
  public TestLambda();
  public static void main(java.lang.String[]);
  private static void lambda$main$0(); // 編譯後生成的
}

由上面的代碼可以看出編譯器根據 Lambda 表達式生成了一個私有靜態方法。

private static void lambda$main$0(); 

使用命令javap -v -p TestLambda列印詳細信息。

public class TestLambda
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#24         // java/lang/Object."<init>":()V
   #2 = Class              #25            // java/lang/Thread
   #3 = InvokeDynamic      #0:#30         // #0:run:()Ljava/lang/Runnable;
   #4 = Methodref          #2.#31         // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
   #5 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #6 = String             #34            // thread
   #7 = Methodref          #35.#36        // java/io/PrintStream.print:(Ljava/lang/String;)V
   #8 = Class              #37            // TestLambda
   #9 = Class              #38            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               LocalVariableTable
  #15 = Utf8               this
  #16 = Utf8               LTestLambda;
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               args
  #20 = Utf8               [Ljava/lang/String;
  #21 = Utf8               lambda$main$0
  #22 = Utf8               SourceFile
  #23 = Utf8               TestLambda.java
  #24 = NameAndType        #10:#11        // "<init>":()V
  #25 = Utf8               java/lang/Thread
  #26 = Utf8               BootstrapMethods
  #27 = MethodHandle       #6:#39         // 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;
  #28 = MethodType         #11            //  ()V
  #29 = MethodHandle       #6:#40         // invokestatic TestLambda.lambda$main$0:()V
  #30 = NameAndType        #41:#42        // run:()Ljava/lang/Runnable;
  #31 = NameAndType        #10:#43        // "<init>":(Ljava/lang/Runnable;)V
  #32 = Class              #44            // java/lang/System
  #33 = NameAndType        #45:#46        // out:Ljava/io/PrintStream;
  #34 = Utf8               thread
  #35 = Class              #47            // java/io/PrintStream
  #36 = NameAndType        #48:#49        // print:(Ljava/lang/String;)V
  #37 = Utf8               TestLambda
  #38 = Utf8               java/lang/Object
  #39 = Methodref          #50.#51        // 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;
  #40 = Methodref          #8.#52         // TestLambda.lambda$main$0:()V
  #41 = Utf8               run
  #42 = Utf8               ()Ljava/lang/Runnable;
  #43 = Utf8               (Ljava/lang/Runnable;)V
  #44 = Utf8               java/lang/System
  #45 = Utf8               out
  #46 = Utf8               Ljava/io/PrintStream;
  #47 = Utf8               java/io/PrintStream
  #48 = Utf8               print
  #49 = Utf8               (Ljava/lang/String;)V
  #50 = Class              #53            // java/lang/invoke/LambdaMetafactory
  #51 = NameAndType        #54:#58        // 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;
  #52 = NameAndType        #21:#11        // lambda$main$0:()V
  #53 = Utf8               java/lang/invoke/LambdaMetafactory
  #54 = Utf8               metafactory
  #55 = Class              #60            // java/lang/invoke/MethodHandles$Lookup
  #56 = Utf8               Lookup
  #57 = Utf8               InnerClasses
  #58 = 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;
  #59 = Class              #61            // java/lang/invoke/MethodHandles
  #60 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #61 = Utf8               java/lang/invoke/MethodHandles
{
  public TestLambda();
    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 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LTestLambda;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #2                  // class java/lang/Thread
         3: dup
         4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
        12: pop
        13: return
      LineNumberTable:
        line 3: 0
        line 4: 13
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  args   [Ljava/lang/String;

  private static void lambda$main$0();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #6                  // String thread
         5: invokevirtual #7                  // Method java/io/PrintStream.print:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
}
SourceFile: "TestLambda.java"
InnerClasses:
     public static final #56= #55 of #59; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #27 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:
      #28 ()V
      #29 invokestatic TestLambda.lambda$main$0:()V
      #28 ()V

如上所示:main() 方法創建線程的一行代碼反編譯後的位元組碼是

 0: new           #2                  // class java/lang/Thread
 3: dup
 4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
 9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
12: pop
13: return

根據4: invokedynamic #3找到常量池第三行

#3 = InvokeDynamic      #0:#30         // #0:run:()Ljava/lang/Runnable;

其中,#0指向BootstrapMethods:的靜態工廠方法LambdaMetafactory.metafactory

BootstrapMethods:
  0: #27 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:
      #28 ()V
      #29 invokestatic TestLambda.lambda$main$0:()V
      #28 ()V

LambdaMetafactory.metafactory會利用 asm 可以為 Lambda 表達式生成內部類,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();
}

為了查看這個內部類,加上生成代理類的參數運行 TestLambda,即java -Djdk.internal.lambda.dumpProxyClasses TestLambda。運行結束後,發現在同級目錄生成了一個class文件TestLambda$$Lambda$1.class。執行javap -v -p TestLambda$$Lambda$1反編譯這個類。

final class TestLambda$$Lambda$1 implements java.lang.Runnable
  minor version: 0
  major version: 52
  flags: ACC_FINAL, ACC_SUPER, ACC_SYNTHETIC
Constant pool:
   #1 = Utf8               TestLambda$$Lambda$1
   #2 = Class              #1             // TestLambda$$Lambda$1
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               java/lang/Runnable
   #6 = Class              #5             // java/lang/Runnable
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = NameAndType        #7:#8          // "<init>":()V
  #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
  #11 = Utf8               run
  #12 = Utf8               Ljava/lang/invoke/LambdaForm$Hidden;
  #13 = Utf8               TestLambda
  #14 = Class              #13            // TestLambda
  #15 = Utf8               lambda$main$0
  #16 = NameAndType        #15:#8         // lambda$main$0:()V
  #17 = Methodref          #14.#16        // TestLambda.lambda$main$0:()V
  #18 = Utf8               Code
  #19 = Utf8               RuntimeVisibleAnnotations
{
  private TestLambda$$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 void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: invokestatic  #17                 // Method TestLambda.lambda$main$0:()V
         3: return
    RuntimeVisibleAnnotations:
      0: #12()
}

由上可見,內部類TestLambda$$Lambda$1實現了 java.lang.Runnable,run()方法即線程啟動後執行的方法,它會觸發執行TestLambda.lambda$main$0:(),即編譯TestLambda時生成的私有靜態方法。

總結

創建線程的 Lambda 表達式在編譯後會在 class 文件新增一個私有靜態方法,運行這個類的期間,會使用 asm 操作位元組碼的技術,動態生成內部類,此內部類實現了 Runnable 介面,真正執行線程,線程方法 run 內部實際上就是調用了編譯後生成的私有靜態方法,從而執行線程代碼。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於重覆的代碼,如果是重覆的字元串,我們會想到提出一個變數。如果是重覆的代碼塊,我們會想到提取出一個方法。 但如果這重覆的代碼塊中有一處或幾處是會變化的,那麼就沒那麼容易提取出一個方法。說起來有點抽象,下麵看一個例子。 一、分頁查詢 寫過CRUD的同學肯定寫過很多分頁查詢,分頁查詢的主要步驟是先校驗 ...
  • 舉個慄子 問題描述 模擬訪問資料庫“新增用戶”和“得到用戶”,用戶類假設只有 ID和Name 兩個欄位。 簡單實現 User SqlServerUser 測試 測試結果 存在問題 如果需要連接別的資料庫,那麼這個寫法無法擴展,下麵使用 工廠方法模式 實現 工廠方法模式實現 IUser SqlServ ...
  • [toc] 前言 Composite設計模式,將物體組合成一個樹結構,讓單個對象和組合對象使用起來都一樣,組合對象負責將實際的操作分發給每一個組件。 這篇博文分析了安卓的View相關的類,它們可以說是用了Composite設計模式。其中分析View的measure,layout,draw是如何從組合 ...
  • 零基礎java開發工程師視頻教程全套,基礎+進階+項目實戰(152G) 共130天,152G 下載地址 ...
  • 周末閑來無事,看到隔壁家的老王在和隔壁家的媳婦玩24點,就進屋看了看。發現老王是真不行啊,那不行,這也不行。 就連個24點都玩不過他媳婦,給他媳婦氣的,啥都不能滿足,這不能,那也不能。 我坐下來和他媳婦玩了兩把,那都是無出其右,把把贏! 我要走的時候,他媳婦還輓留我多玩幾把,有意思。 為了能... ...
  • 書評: 感謝作者和譯者,很好的手把手的一個新手編程體驗書,消除編程物質恐懼感,線上看的liam huang翻譯的版,不確定看的是第幾版,有一些加分題沒有做,第五十題黑手黨外星人飛船做起來有點壓力,準備轉去codeacademy / think python和a byte of python在進行一些 ...
  • 前段時間自己使用 redis 開發的時候,搞了一個 docker ,然後直接開放連接沒有密碼,其實一開始我就知道會被黑產掃到然後給我種馬,但是把因為也是測試服務,其實也沒怎麼上心,於是就放任自由了,結果第二天果然收到了一份新鮮的木馬。然後簡單對其入侵做了一個分析,結果發現沒有能攻擊成功,但是既然木馬 ...
  • Spring 可以說是最流行的 Java 框架之一,也是一隻需要馴服的強大野獸。雖然它的基本概念相當容易掌握,但成為一名強大的 Spring 開發者仍需要很多時間和努力。 在本文中,我們將介紹 Spring 中一些常見的錯誤,特別是面向 Web 應用程式和 Spring Boot。正如 Spring ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...