首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 反編譯實戰:揭秘 i++ 與 ++i 運算順序、匿名內部類初始化陷阱

     編輯:Java 綜合教學

Java 反編譯實戰:揭秘 i++ 與 ++i 運算順序、匿名內部類初始化陷阱

通過 javap -verbose 反編譯字節碼,能直觀理解語法糖背後的 JVM 執行邏輯,解決源碼層面難以判斷的疑難問題。

案例一:連續自增表達式求值順序

int i = 0;
int y = i++ + ++i;  // 結果是多少?
int z = i++ + ++i + ++i + ++i + i++ + ++i; // 更複雜

反編譯字節碼分析 (y 表達式)

 0: iconst_0           // 壓入 0
 1: istore_0           // 存入局部變量 0 (i=0)
 2: iload_0            // 載入 i (0) → 棧頂
 3: iinc 0 1           // i++ : i 變 1
 6: iinc 0 1           // ++i : i 變 2
 9: iload_0            // 載入 i (2) → 棧頂
10: iadd               // 棧頂兩值相加 (0+2=2)
11: istore_1           // 存入 y

結論:Java 採用操作數棧評估策略——i++ 先壓棧後自增,++i 先自增後壓棧,最後棧頂相加。y = 0 + 2 = 2,z = 0+2+3+4+4+6 = 19。注意:C/C++ 編譯器可能產生不同結果,不可混淆。

案例二:匿名內部類初始化陷阱

正確寫法:實例初始化塊中調用 this.put()

public static Map<String,String> m = new HashMap<>() {{
    put("key1", "value1"); // 隱式 this.put()
}};

反編譯生成兩個類:Outer.class、Outer$1.class (匿名內部類)。靜態初始化塊 <clinit> 中:new Outer$1() → dup → invokespecial Outer$1() → putstatic m。內部類構造器先調用 HashMap() 再 invokevirtual put(),此時 m 尚未賦值但內部類已持有自身引用,this.put() 正常工作。

錯誤寫法:誤用外部類靜態字段 m.put()

public static Map<String,String> m = new HashMap<>() {{
    m.put("key1", "value1"); // 顯式引用外部靜態字段 m
}};

反編譯關鍵差異:內部類構造器中 getstatic m → invokeinterface Map.put。執行順序:

  1. <clinit> 執行 new Outer$1()
  2. 調用 Outer$1() 構造器
  3. 構造器執行 getstatic m —— 此時 m 仍為 null (putstatic 尚未執行!)
  4. 調用 null.put() → NullPointerException → 包裝為 ExceptionInInitializerError

反編譯實操指南

# 1. 編譯
javac TestIpp.java

# 2. 反編譯 (verbose 輸出完整字節碼、行號表、局部變量表)
javap -verbose TestIpp

# 3. Eclipse 直接拖入 .class 文件查看 (需項目編譯產出的 class)

核心心法

💡 會當凌絕頂,一覽眾山小:掌握字節碼、棧幀、類加載機制,源碼層面的「玄學問題」瞬間透明化。反編譯不是黑魔法,是理解 JVM 執行模型的必經之路。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved