通過 javap -verbose 反編譯字節碼,能直觀理解語法糖背後的 JVM 執行邏輯,解決源碼層面難以判斷的疑難問題。
int i = 0;
int y = i++ + ++i; // 結果是多少?
int z = i++ + ++i + ++i + ++i + i++ + ++i; // 更複雜
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++ 編譯器可能產生不同結果,不可混淆。
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() 正常工作。
public static Map<String,String> m = new HashMap<>() {{
m.put("key1", "value1"); // 顯式引用外部靜態字段 m
}};
反編譯關鍵差異:內部類構造器中 getstatic m → invokeinterface Map.put。執行順序:
<clinit> 執行 new Outer$1()Outer$1() 構造器getstatic m —— 此時 m 仍為 null (putstatic 尚未執行!)null.put() → NullPointerException → 包裝為 ExceptionInInitializerError# 1. 編譯
javac TestIpp.java
# 2. 反編譯 (verbose 輸出完整字節碼、行號表、局部變量表)
javap -verbose TestIpp
# 3. Eclipse 直接拖入 .class 文件查看 (需項目編譯產出的 class)
💡 會當凌絕頂,一覽眾山小:掌握字節碼、棧幀、類加載機制,源碼層面的「玄學問題」瞬間透明化。反編譯不是黑魔法,是理解 JVM 執行模型的必經之路。