1. 程式人生 > >try...catch語句中return和finally到底誰先執行

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模式來觀察這一過程,結果如下:

  1. 在 throw new Exception() 處打上斷點後,可以看到方法進入了 catch 語句塊中,並開始執行 return 語句。此時 result = 0,return 後的表示式還沒有執行。
    開始執行return語句

  2. Step Over後,可以看到 result 的值變為 1(執行了 ++result),並開始執行 finally 語句塊
    執行return後表示式

  3. 繼續Step Over,result 的值變為 3(執行了 result += 2),方法又跳回了 return 語句
    執行finally語句塊

  4. 接著,test() 方法執行完畢,可以看到此時方法的返回值為 1
    方法執行完畢

  5. 最後,控制檯輸出 “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的引數傳遞到底是值傳遞還是引用傳遞.)