天天看點

return finally執行順序_Java中return和finally到底哪個先執行

return finally執行順序_Java中return和finally到底哪個先執行

return和finally到底哪個先執行。下面先來看一段簡單 地 源碼:

public class ReturnFinallyDemo { public static void main(String[] args) { System.out.println(case1()); } public static int case1() { int x; try { x = 1; return x; } finally { x = 3; } }}# 輸出1

上述代碼的輸出可以簡單地得出結論:return在finally之前執行,我們來看下位元組碼層面上發生了什麼事情。下面截取case1方法的部分位元組碼,并且對照源碼,将每個指令的含義注釋在後面:

0: iconst_1 // 将常量1推入操作數棧頂1: istore_0 // 彈出棧頂元素(1),儲存到局部變量表slot[0],此時slot[0]=1。這兩條指令對應源碼:x = 1;2: iload_0 // 将局部變量表slot[0]的值推入操作數棧頂,也就是說把上面x的值推入棧頂3: istore_1 // 彈出棧頂元素(1),儲存到局部變量表slot[1],此時slot[1]=1。其實,此時就已經把要return的值準備好了4: iconst_3 // 将常量3推入操作數棧頂,這一條指令開始,其實是開始執行finally中的代碼了5: istore_0 // 彈出棧頂元素(3),儲存到局部變量表slot[0],此時slot[0]=3。這兩個指令對應源碼:x = 3;這裡要注意的是,雖然都是更新了x的值,但是finally中的x和try中x的指派,儲存在了不同的局部變量表中6: iload_1 // 将局部變量表slot[1]的值推入操作數棧頂,此時棧頂元素的值為1,是第3行指令儲存的值7: ireturn // 将操作數棧頂的值傳回給調用方

從位元組碼來看,似乎又是finally的代碼先執行了,因為ireturn指令确實是在最後執行的,是以傳回什麼樣的值不在于誰先執行,而在于ireturn指令傳回的操作數棧頂的元素是何時儲存的。在上述代碼環境中,是try代碼塊中 給 x指派的版本,也就是緊接着return語句後面的x所儲存的版本。

下面再來看一個稍微複雜點的場景:

public static int case2() { int x; try { x = 1; return ++x; } finally { x = 3; }}# 輸出2

有了上面的分析,這個就很好了解了,我們還是來看下位元組碼:

0: iconst_1 // 将常量1推入操作數棧頂1: istore_0 // 彈出棧頂元素(1),儲存到局部變量表slot[0],此時slot[0]=1。這兩條指令對應源碼:x = 1;2: iinc 0, 1 // 對局部變量表slot[0]進行自增(+1)操作,此時slot[0]=2,對應源碼:++x;是以,可以看出return後面的表達式先執行5: iload_0 // 将局部變量表slot[0]的值推入操作數棧頂,也就是說把上面x的值(2)推入棧頂6: istore_1 // 彈出棧頂元素(2),儲存到局部變量表slot[1],此時slot[1]=2。其實,此時就已經把要return的值準備好了7: iconst_3 // 将常量3推入操作數棧頂,這一條指令開始,其實是開始執行finally中的代碼了8: istore_0 // 彈出棧頂元素(3),儲存到局部變量表slot[0],此時slot[0]=3。這兩個指令對應源碼:x = 3;這裡要注意的是,雖然都是更新了x的值,但是finally中的x和try中x的指派,儲存在了不同的局部變量表中9: iload_1 // 将局部變量表slot[1]的值推入操作數棧頂,此時棧頂元素的值為2,是第6行指令儲存的值,也就是經過++x之後的值10: ireturn // 将操作數棧頂的值傳回給調用方

從上述代碼可以看出,return後面的指令先執行,然後儲存到局部變量表,接着執行finally中的語句,最後執行return指令本身。

總結一下,return指令是最後執行的,如果return後面有表達式,則執行完表達式之後就執行finally中的語句,最後再執行return指令。是以說finally和return到底哪個先執行:return指令後面如果有表達式或方法調用的話,先執行,然後執行finally,最後執行return指令。就像上面的程式示範的結果,不能光從x的指派來看最終傳回結果,從指令層面看,兩次對x的指派,儲存在局部變量表的位置不一樣。

最後,再來看一個平時不會這麼去寫的場景:

public static int case3() { int x; try { x = 1; return ++x; } finally { x = 3; return x; }}# 輸出3

這是一個finally傳回結果的示例,平時不建議這麼寫,我們同樣從位元組碼的角度來分析下:

0: iconst_1 // 将常量1推入操作數棧頂1: istore_0 // 彈出棧頂元素(1),儲存到局部變量表slot[0],此時slot[0]=1。這兩條指令對應源碼:x = 1;2: iinc 0, 1 // 對局部變量表slot[0]進行自增(+1)操作,此時slot[0]=2,對應源碼:++x;是以,可以看出return後面的表達式先執行5: iload_0 // 将局部變量表slot[0]的值推入操作數棧頂,也就是說把上面x的值(2)推入棧頂6: istore_1 // 彈出棧頂元素(2),儲存到局部變量表slot[1],此時slot[1]=2。7: iconst_3 // 将常量3推入操作數棧頂,這一條指令開始,其實是開始執行finally中的代碼了8: istore_0 // 彈出棧頂元素(3),儲存到變量表slot[0],此時slot[0]=3。這兩個指令對應源碼:x = 39: iload_0 // 将局部變量表slot[0]的值(3)推入操作數棧,這是跟之前不一樣的地方,ireturn傳回的值選擇的局部變量表不一樣10: ireturn

從位元組碼以及解釋來看,直接忽略了try語句塊中的return指令,這樣的代碼會讓人産生疑惑,是以平時不建議這麼寫。

歡迎關注我們!

部分文字與圖檔來源于網絡,如有版權請聯系删除!