package Dec2016;
public class Ques1215 {
static final int num1 = 2;
static final int num2 = 3;
static int num3 = 2;
static int num4 = 3;
public static void main(String[] args) {
int result1 = num1 * num2; // 行 1
int result2 = num3 * num4; // 行 2
}
}
問題:主程序中行 1 和行 2 在字節碼層面有什麼本質區別?
行 1 (
num1 * num2) 在編譯期即被解析為常量6,直接嵌入字節碼;
行 2 (num3 * num4) 在運行期執行乘法指令計算結果。
變量必須同時滿足:
final 修飾2, 3.14f, 'a', true)"hello")2 + 3, 5 * 10)"a" + "b")(int) 2.5)| 代碼 | 編譯期處理 | 運行期字節碼 |
|---|---|---|
static final int a = 2; | 標記為 ConstantValue 屬性 | bipush 2 (直接推入常量) |
static final int b = a + 1; | 計算為 3,標記 ConstantValue | bipush 3 |
static int c = 2; | 無特殊處理 | getstatic c → iconst_2 → putstatic |
int r = a * b; | 常量折疊:計算 6,內聯到使用處 | bipush 6 (無乘法指令!) |
int r = c * d; | 無法優化 | getstatic c → getstatic d → imul → istore |
> javac Ques1215.java
> javap -c Ques1215
// 行 1 字節碼
0: bipush 6 // 直接推入常量 6,無乘法運算
2: istore_1
// 行 2 字節碼
3: getstatic #2 // 獲取 num3
6: getstatic #3 // 獲取 num4
9: imul // 整數乘法指令
10: istore_2
// A.jar
public class Consts {
public static final int VERSION = 1;
}
// B.jar 依賴 A.jar
public class App {
public static void main(String[] args) {
System.out.println(Consts.VERSION); // 編譯期內聯為 1
}
}
// 修改 A.jar: VERSION = 2,重新打包 A.jar,但不重新編譯 B.jar
// 運行 B.jar 輸出仍為 1!因為常量已內聯到 B.jar 字節碼中
最佳實踐:對外發布的 API 常量,若未來可能變更,不要用
static final基本類型/字符串,改用public static int getVersion()或非 final 字段 + 文檔約定。
static final List LIST = new ArrayList<>(); // 引用不可變,但對象可變
LIST.add("hello"); // ✅ 編譯通過,運行正常
static final Date DATE = new Date(); // 引用不可變,但 Date 可變
DATE.setTime(0); // ✅ 危險!破壞不可變性假設
static final + 編譯期常量表達式 = 宏變量,編譯期內聯,零開銷static 字段 = 運行期讀取/寫入,靈活但有開銷static final,除非真正不可變 (如 Math.PI)final 僅保證引用不變,不保證對象不可變下期預告:String.intern() 與字符串常量池的那些事兒。