try...catch語句中return和finally到底誰先執行
寫在開頭
這個問題真的困擾了我很久,感覺簡直像一個哲學(?)問題。
私下和朋友們對這個問題討論了很久,又在網上查找了很多相關資料,終於還是把這個問題理清楚了。(自認為 )
我的結論是:在try…catch語句中,當程式執行完return後的表示式後,會轉而執行finally語句塊,最後再繼續執行return。
…這個答案看起來是不是還是很哲學?下面我來給出詳細解釋。
return和finally的定義
首先來看一下 return 和 finally 的定義:
- return:方法的結束標誌,它導致該方法退出,並返回return後的那個值(在返回型別為void的方法裡面,也有個被省略的return)。
- finally:作為異常處理的一部分,它只能用在try…catch語句中,並且附帶一個語句塊,表示這段語句最終一定會被執行(不管有沒有丟擲異常),經常被用在需要釋放資源的情況下。
return作為方法的“結束標誌”,也就是說執行完return語句後,方法也就結束了。那麼return看起來就是那個最後才執行的語句。
finally表示後面的語句塊“最終一定會被執行”,細細品味,似乎只是在說明這段語句塊肯定會執行的,但是不是最後才執行的就不一定了。
下面我們用程式碼來試驗一下。
試驗程式碼
定義如下方法,執行並輸出
public static int test () {
int result = 0;
try {
throw new Exception();
}catch (Exception e) {
return ++result;
}finally {
result += 2;
}
}
public static void main(String[] args) {
System.out.println("result = " + test());
}
輸出的結果為 result = 1。
這個結果說明 finally 語句塊中的內容並沒有奏效。通過debug模式來觀察這一過程,結果如下:
-
在 throw new Exception() 處打上斷點後,可以看到方法進入了 catch 語句塊中,並開始執行 return 語句。此時 result = 0,return 後的表示式還沒有執行。
-
Step Over後,可以看到 result 的值變為 1(執行了 ++result),並開始執行 finally 語句塊
-
繼續Step Over,result 的值變為 3(執行了 result += 2),方法又跳回了 return 語句
-
接著,test() 方法執行完畢,可以看到此時方法的返回值為 1
-
最後,控制檯輸出 “result = 1”。
結論
由此可見,執行的順序是:return 後的表示式(++result) → finally 語句塊(result += 2)→ return 返回值
但是為什麼 finally 語句塊並沒有奏效呢?
這是因為當代碼執行完 return 語句後的表示式(++result)後,會將要返回的值(result = 1)先存入一個區域性變量表(假設這個變數名為 returnedValue,即 returnedValue = 1)。然後再執行 finally 程式碼塊(result += 2),這個程式碼塊修改的是 result 的值而不是 returnedValue 。上面程式碼中 result 作為返回值,但 result 變數本身與返回值 returnedValue 存放在不同的位置,所以修改了 result 後,returnedValue 並未改變。
補充
這段測試程式碼中,用來測試的返回值是基本資料型別,最後返回值並沒有 finally 語句塊被修改。
但是,當返回值為引用型別時,finally 語句塊是可以修改最後的返回值的。舉個例子:
定義一個僱員類
public class Employee {
private int salary;
public Employee(int salary) {
this.salary = salary;
}
//省略getter和setter
}
嘗試在 finally 語句塊中修改僱員的狀態
public static Employee test() {
Employee emp = new Employee(100);
try {
throw new Exception();
}catch (Exception e) {
return emp;
}finally {
emp.setSalary(200);
}
}
public static void main(String[] args) {
System.out.println("emp.salary = " + test().getSalary());
}
輸出結果
這是因為 emp 和 returnedValue 同時指向同一個物件,所以對 emp 的修改會影響到最終的返回值。(具體的原因可以參考我的上一篇文章:Java的引數傳遞到底是值傳遞還是引用傳遞.)