今天去逛論壇 時發現了一個頗有趣的問題:html
誰能給我我解釋一下這段程序的結果爲何是:2.而不是:3java
代碼以下:程序員
class Test { public int aaa() { int x = 1; try { return ++x; } catch (Exception e) { } finally { ++x; } return x; } public static void main(String[] args) { Test t = new Test(); int y = t.aaa(); System.out.println(y); } }
看了問題後,得出瞭如下幾個問題:oracle
剛看到這個問題後。忽然發現基礎不夠紮實,竟然來第一個都答不出來。。。(不知道還有木有和我也同樣也回答不出以上的問題的? 若是有請在評論裏告訴我一聲,讓我知道,我並不孤獨~~)app
根據已有的知識知道:
return 是能夠看成終止語句來用的,咱們常常用它來跳出當前方法,並返回一個值給調用方法。而後該方法就結束了,不會執行return下面的語句。
finally :不管try語句發生了什麼,不管拋出異常仍是正常執行。finally語句都會執行。
那麼問題來了。。。。在try語句裏使用return後,finally是否還會執行?finally必定會執行的說法是否還成立?若是成立,那麼先執行return仍是先執行finally?jvm
在求知慾的驅動下,我繼續進行更深的探索,果斷打開了Oracle的主頁,翻閱了java 官方教程的finally語句。發現了官方教程對這個特殊狀況有說明:ide
The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.函數
Note: If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues..net
我的簡單翻譯:線程
當try語句退出時確定會執行finally語句。這確保了即便發了一個意想不到的異常也會執行finally語句塊。可是finally的用處不只是用來處理異常——它可讓程序員不會由於return、continue、或者break語句而忽略了清理代碼。把清理代碼放在finally語句塊裏是一個很好的作法,即使可能不會有異常發生也要這樣作。
注意,當try或者catch的代碼在運行的時候,JVM退出了。那麼finally語句塊就不會執行。一樣,若是線程在運行try或者catch的代碼時被中斷了或者被殺死了(killed),那麼finally語句可能也不會執行了,即便整個運用還會繼續執行。
從上面的官方說明,咱們知道不管try裏執行了return語句、break語句、仍是continue語句,finally語句塊還會繼續執行。同時,在stackoverflow裏也找到了一個答案,咱們能夠調用System.exit()來終止它:
finally will be called.
The only time finally won't be called is: if you call System.exit(), another thread interrupts current one, or if the JVM crashes first.
另外,在java的語言規範有講到,若是在try語句裏有return語句,finally語句仍是會執行。它會在把控制權轉移到該方法的調用者或者構造器前執行finally語句。也就是說,使用return語句把控制權轉移給其餘的方法前會執行finally語句。
咱們依然使用上面的代碼做爲例子。首先,分別在如下三行代碼前加上斷點:
而後以debug模式運行代碼。
剛開始時,效果以下圖:
按一下F6,咱們能夠發現,程序已經執行到 return ++x;,但還沒執行該語句,此刻x=1
繼續按一下F6,程序執行到 ++x;,但還沒執行該語句,所以此時的x=2(剛執行完return ++x語句的++x,但沒執行return)
繼續按一下F6,此時,咱們發現程序又跳回到 return +xx 這一行,此刻x=3(執行了finally語句裏的++x)
從上面過程當中能夠看到,
看到這,咱們可能會再次糾結起來了。從上面的驗證能夠看出,finally語句執行了,並且x的值也確實加到3了,那麼爲何y是2呢?
翻看官方的jvm規範就會把一切的謎團解開了:
If the try clause executes a return, the compiled code does the following:
- Saves the return value (if any) in a local variable.
- Executes a jsr to the code for the finally clause.
- Upon return from the finally clause, returns the value saved in the local variable.
簡單翻譯下:
若是try語句裏有return,那麼代碼的行爲以下:
1.若是有返回值,就把返回值保存到局部變量中
2.執行jsr指令跳到finally語句裏執行
3.執行完finally語句後,返回以前保存在局部變量表裏的值
根據上面的說明就能夠輕易地解釋爲何是2了。
當執行到return ++x;時,jvm在執行完++x後會在局部變量表裏另外分配一個空間來保存當前x的值。
注意,如今還沒把值返回給y,而是繼續執行finally語句裏的語句。等執行完後再把以前保存的值(是2不是x)返回給y。
因此就有了y是2不是3的狀況。
其實這裏還有一點要注意的是,若是你在finally裏也用了return語句,好比return +xx。那麼y會是3。由於規範規定了,當try和finally裏都有return時,會忽略try的return,而使用finally的return。
查看Test.class的字節碼咱們一樣也能夠很輕鬆地知道爲何是2而不是3:
大概講講指令操做順序:
iconst_1: 把常數1進棧 ---> istore_1: 棧頂元素出棧並把元素保存在本地變量表的第二個位置裏(下標爲1的位置裏) ---> iinc 1, 1 : 本地變量表的第二個元素自增1 --->iload_1:第二個元素進棧 ---> istore_2:棧頂元素出棧並把元素保存在本地變量表的第2個位置裏 ---> iinc 1, 1 : 本地變量表的第二個元素自增1 ---> iload_2:第二個元素進棧 (注意,此時棧頂元素爲2)---> ireturn:返回棧頂元素。
後面的指令是要在2-7行出現異常時在跳到12行的,這個例子沒出現異常,不用關注。
上面流程棧和本地變量表的狀況以下圖:
參考資料:
Java虛擬機規範
java 官方教程的finally語句
IBM的Java字節碼教程
深刻理解Java虛擬機(第2版)