你真的知道嗎?catch、finally和return哪個(gè)先執(zhí)行

我的一位朋友前陣子遇到一個(gè)問題,問題的核心就是try……catch……finally中catch和finally代碼塊到底哪個(gè)先執(zhí)。這個(gè)問題看起來很簡單,當(dāng)然是“catch先執(zhí)行、finally后執(zhí)行”了?真的是這樣嗎?
有下面一段C#代碼,請問這段代碼的執(zhí)行結(jié)果是什么?
A()方法的try代碼塊中拋出了異常,而A方法沒有處理這個(gè)異常,所以Main方法的catch代碼塊會(huì)捕獲這個(gè)異常,但是A()方法中又有finally代碼塊,那么到底是異常拋出后先執(zhí)行Main方法的catch代碼塊呢還是先執(zhí)行A()方法中的finally代碼塊呢?運(yùn)行一下程序就能看出來,是finally代碼塊執(zhí)行,結(jié)果如下所示。
為什么呢?這需要從方法調(diào)用的異常對象如何傳遞給被調(diào)用方法講起。在一段代碼調(diào)用一個(gè)方法的時(shí)候,被調(diào)用的方法會(huì)把返回值、異常對象等放到一個(gè)特定的位置,這個(gè)位置叫做Stack Frame,調(diào)用者代碼會(huì)從這個(gè)特定的位置獲得被調(diào)用方法的返回值、異常對象等信息。因此,無論是throw異常的時(shí)候還是return返回值的時(shí)候,被調(diào)用的方法只是把異常對象或者返回值放到了這個(gè)特定的位置,在return或者throw執(zhí)行之后,如果方法中還有finally等沒有執(zhí)行完成的代碼,那么這些代碼仍然會(huì)在return、throw之后繼續(xù)執(zhí)行,然后方法執(zhí)行才會(huì)結(jié)束,之后調(diào)用這個(gè)方法的代碼才會(huì)從Stack Frame中讀取到返回值或者獲取到被調(diào)用的方法拋出的異常對象。因此,上面的代碼才會(huì)先執(zhí)行finally然后才執(zhí)行catch。
明白了這個(gè)道理,請回答一下,下面代碼的執(zhí)行結(jié)果是什么?
上面這是一段很特殊的代碼,在try代碼塊中拋出了一個(gè)異常(信息是aa),在finally中也拋出了一個(gè)異常(信息是bb),那么程序?qū)嶋H打印出來的異常信息是什么呢?上面程序執(zhí)行結(jié)果是“bb”。通過上面的分析不難理解其原理:try代碼塊中的throw new Exception("aa")把方法的異常對象設(shè)置為Exception("aa"),而finall代碼塊中的throw new Exception("bb")又把方法的異常對象修改為Exception("bb"),因此最終方法拋出的異常對象是Exception("bb")。
接下來,我們再來捉弄一下方法的返回值,我們嘗試在finally代碼塊中修改方法的返回值。不幸的是(也可以說,幸運(yùn)的是),C#禁止我們在finally代碼塊使用return語句,不過我們可以在Java中做這樣的嘗試,如下Java代碼所示:
我們在try代碼塊中通過return 1把方法的返回值設(shè)置為1,但是在finally代碼塊中又把方法的返回值設(shè)置為2,因此方法的最終返回值就是2。
綜上所述,一個(gè)方法中通過return設(shè)定返回值或者throw拋出異常的時(shí)候,方法并沒有立即返回,只是在Stack Frame上保存了這個(gè)返回值或者異常對象,然后會(huì)繼續(xù)執(zhí)行finally中的代碼,如果我們在finally代碼塊中修改了返回值或者拋出了新的異常,那么最終的調(diào)用中獲得的返回值或者捕獲的對象就是修改后的返回值或者異常對象。