public static int test() {
int t = 0;
try {
return t;
} finally {
++t;
}
}
問題:main 中調用 test() 輸出什麼?
直覺答案:1(因為 finally 在 return 前執行,t 自增變 1)
實際輸出:0
JVM 執行 return t 時:
t 的值(此時 t=0)int t 是基本類型,finally 中 ++t 修改的是局部變量 t,不影響 已經保存到返回值槽位的 副本值 (0)。
test() 執行流程:
┌─────────────────────────────────────┐
│ int t = 0 │
├─────────────────────────────────────┤
│ try { │
│ return t; ──→ 保存返回值槽 = 0 │ ← 關鍵:這裡確定了返回值!
│ } │
├─────────────────────────────────────┤
│ finally { │
│ ++t; ──→ t 變為 1 │ ← 修改的是局部變量,不影響返回值槽
│ } │
├─────────────────────────────────────┤
│ 返回返回值槽中的值 (0) │
└─────────────────────────────────────┘
public static User testObj() {
User user = new User();
user.setAge(0);
try {
return user; // 保存的是引用副本(指向同一堆對象)
} finally {
user.setAge(1); // 修改堆中對象屬性
}
}
// 輸出:1
// 原因:返回值槽保存的是引用地址,finally 修改的是堆中同一個對象
public static int testFinallyReturn() {
int t = 0;
try {
return t; // 保存返回值 0
} finally {
++t;
return t; // 直接返回 1,覆蓋 try 的返回值!
}
}
// 輸出:1
結論:finally 中的 return 會覆蓋 try/catch 的返回值,這是極其危險的反模式,嚴禁在 finally 中使用 return。
《Java 虛擬機規範》規定:
finally 塊在方法返回之前執行。但「返回」包含兩個階段:確定返回值 和 執行返回指令。finally 在兩者之間執行。
更精確的描述:finally 比 return 指令先執行完畢,但返回值在 finally 前已確定(基本類型)或引用已確定(引用類型)。
// test() 字節碼片段
0: iconst_0 // t = 0
1: istore_1
2: iload_1 // 加載 t (0)
3: istore_2 // 保存到局部變量槽 2 (返回值槽)
4: iinc 1, 1 // finally: t++ (t=1)
7: iload_2 // 加載返回值槽 (0)
8: ireturn // 返回 0
可見:iload_1 (t=0) → istore_2 (保存返回值) → iinc (finally修改t) → iload_2 (讀取返回值) → ireturn。
「try 中 return 時,返回值先被計算並保存到局部變量表的返回值槽位。然後執行 finally。finally 修改基本類型局部變量不影響已保存的返回值;修改引用類型指向的對象內容會影響最終結果。若 finally 有 return,會覆蓋 try 的返回值,這是嚴重反模式。」
這道題考察:JVM 返回機制、值傳遞/引用傳遞、finally 執行時機、字節碼層面理解。記住核心:**返回值在 return 語句時確定,finally 執行不改變已確定的基本類型返回值**。