我的一位朋友前陣子遇到一個問題,問題的核心就是try……catch……finally中catch和finally代碼塊到底哪個先執。這個問題看起來很簡單,當然是“catch先執行、finally後執行”了?真的是這樣嗎? 有下麵一段C#代碼,請問這段代碼的執行結果是什麼? public static ...
我的一位朋友前陣子遇到一個問題,問題的核心就是try……catch……finally中catch和finally代碼塊到底哪個先執。這個問題看起來很簡單,當然是“catch先執行、finally後執行”了?真的是這樣嗎?
有下麵一段C#代碼,請問這段代碼的執行結果是什麼?
public static void Main(string[] args) { try { A(); } catch { Console.WriteLine("catch!!!"); } } static void A() { try { throw new Exception(); } finally { Console.WriteLine("finally!!!"); } }
A()方法的try代碼塊中拋出了異常,而A方法沒有處理這個異常,所以Main方法的catch代碼塊會捕獲這個異常,但是A()方法中又有finally代碼塊,那麼到底是異常拋出後先執行Main方法的catch代碼塊呢還是先執行A()方法中的finally代碼塊呢?運行一下程式就能看出來,是finally代碼塊執行,結果如下所示。
finally!!!
catch!!!
為什麼呢?這需要從方法調用的異常對象如何傳遞給被調用方法講起。在一段代碼調用一個方法的時候,被調用的方法會把返回值、異常對象等放到一個特定的位置,這個位置叫做Stack Frame,調用者代碼會從這個特定的位置獲得被調用方法的返回值、異常對象等信息。因此,無論是throw異常的時候還是return返回值的時候,被調用的方法只是把異常對象或者返回值放到了這個特定的位置,在return或者throw執行之後,如果方法中還有finally等沒有執行完成的代碼,那麼這些代碼仍然會在return、throw之後繼續執行,然後方法執行才會結束,之後調用這個方法的代碼才會從Stack Frame中讀取到返回值或者獲取到被調用的方法拋出的異常對象。因此,上面的代碼才會先執行finally然後才執行catch。
明白了這個道理,請回答一下,下麵代碼的執行結果是什麼?
public static void Main(string[] args) { try { A(); } catch(Exception ex) { Console.WriteLine(ex.Message); } } static void A() { try { throw new Exception("aa"); } finally { throw new Exception("bb"); } }
上面這是一段很特殊的代碼,在try代碼塊中拋出了一個異常(信息是aa),在finally中也拋出了一個異常(信息是bb),那麼程式實際列印出來的異常信息是什麼呢?上面程式執行結果是“bb”。通過上面的分析不難理解其原理:try代碼塊中的throw new Exception("aa")把方法的異常對象設置為Exception("aa"),而finall代碼塊中的throw new Exception("bb")又把方法的異常對象修改為Exception("bb"),因此最終方法拋出的異常對象是Exception("bb")。
接下來,我們再來捉弄一下方法的返回值,我們嘗試在finally代碼塊中修改方法的返回值。不幸的是(也可以說,幸運的是),C#禁止我們在finally代碼塊使用return語句,不過我們可以在Java中做這樣的嘗試,如下Java代碼所示:
public static void main(String[] args) { System.out.println(A()); } static int A() { try { return 1; } finally { return 2; } }
我們在try代碼塊中通過return 1把方法的返回值設置為1,但是在finally代碼塊中又把方法的返回值設置為2,因此方法的最終返回值就是2。
綜上所述,一個方法中通過return設定返回值或者throw拋出異常的時候,方法並沒有立即返回,只是在Stack Frame上保存了這個返回值或者異常對象,然後會繼續執行finally中的代碼,如果我們在finally代碼塊中修改了返回值或者拋出了新的異常,那麼最終的調用中獲得的返回值或者捕獲的對象就是修改後的返回值或者異常對象。