你真的了解try{ return }finally{}中的return?
你真的了解try{ return }finally{}中的return?
今天去逛論壇 時(shí)發(fā)現(xiàn)了一個(gè)很有趣的問題:
誰能給我我解釋一下這段程序的結(jié)果為什么是:2.而不是:3
代碼如下:
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); } }看了問題后,得出了以下幾個(gè)問題:
- 如果在?try?語句塊里使用?return?語句,那么?finally?語句塊還會(huì)執(zhí)行嗎?(果你的答案是不會(huì)執(zhí)行,請務(wù)必要看下去 ^_^)
- 如果執(zhí)行,那么是怎樣實(shí)現(xiàn)既執(zhí)行?return?又執(zhí)行?finally?的呢?(如果你的答案是不知道,請繼續(xù)看下去!!)
- 上面的程序輸出為什么是2?( 如果不知道,繼續(xù)看下去~~)
- 在網(wǎng)上看到還有人還問“是先執(zhí)行return還是先執(zhí)行finally?”的
(個(gè)人覺得,如果知道finally會(huì)執(zhí)行就可以得出是,先執(zhí)行finally再執(zhí)行return的。因?yàn)?#xff0c;如果先執(zhí)行return,那么整個(gè)函數(shù)都跳出了,那么還怎么執(zhí)行finally?^_^)
剛看到這個(gè)問題后。突然發(fā)現(xiàn)基礎(chǔ)不夠扎實(shí),居然來第一個(gè)都答不出來。。。(不知道還有木有和我也一樣也回答不出以上的問題的? 如果有請?jiān)谠u論里告訴我一聲,讓我知道,我并不孤獨(dú)~~)
根據(jù)已有的知識知道:
return?是可以當(dāng)作終止語句來用的,我們經(jīng)常用它來跳出當(dāng)前方法,并返回一個(gè)值給調(diào)用方法。然后該方法就結(jié)束了,不會(huì)執(zhí)行return下面的語句。
finally?:無論try語句發(fā)生了什么,無論拋出異常還是正常執(zhí)行。finally語句都會(huì)執(zhí)行。
那么問題來了。。。。在try語句里使用return后,finally是否還會(huì)執(zhí)行?finally一定會(huì)執(zhí)行的說法是否還成立?如果成立,那么先執(zhí)行return還是先執(zhí)行finally?
驗(yàn)證 finally 語句是否會(huì)執(zhí)行,以及 return 和 finally的執(zhí)行順序
在求知欲的驅(qū)動(dòng)下,我繼續(xù)進(jìn)行更深的探索,果斷打開了Oracle的主頁,翻閱了java 官方教程的finally語句。發(fā)現(xiàn)了官方教程對這個(gè)特殊情況有說明:
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.
個(gè)人簡單翻譯:
當(dāng)try語句退出時(shí)肯定會(huì)執(zhí)行finally語句。這確保了即使發(fā)了一個(gè)意想不到的異常也會(huì)執(zhí)行finally語句塊。但是finally的用處不僅是用來處理異?!梢宰尦绦騿T不會(huì)因?yàn)閞eturn、continue、或者break語句而忽略了清理代碼。把清理代碼放在finally語句塊里是一個(gè)很好的做法,即便可能不會(huì)有異常發(fā)生也要這樣做。
注意,當(dāng)try或者catch的代碼在運(yùn)行的時(shí)候,JVM退出了。那么finally語句塊就不會(huì)執(zhí)行。同樣,如果線程在運(yùn)行try或者catch的代碼時(shí)被中斷了或者被殺死了(killed),那么finally語句可能也不會(huì)執(zhí)行了,即使整個(gè)運(yùn)用還會(huì)繼續(xù)執(zhí)行。
從上面的官方說明,我們知道無論try里執(zhí)行了return語句、break語句、還是continue語句,finally語句塊還會(huì)繼續(xù)執(zhí)行。同時(shí),在stackoverflow里也找到了一個(gè)答案,我們可以調(diào)用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的語言規(guī)范有講到,如果在try語句里有return語句,finally語句還是會(huì)執(zhí)行。它會(huì)在把控制權(quán)轉(zhuǎn)移到該方法的調(diào)用者或者構(gòu)造器前執(zhí)行finally語句。也就是說,使用return語句把控制權(quán)轉(zhuǎn)移給其他的方法前會(huì)執(zhí)行finally語句。
個(gè)人驗(yàn)證
我們依然使用上面的代碼作為例子。首先,分別在以下三行代碼前加上斷點(diǎn):
- int x = 1;
- return ++x;
- ++x;
然后以debug模式運(yùn)行代碼。
剛開始時(shí),效果如下圖:
按一下F6,我們可以發(fā)現(xiàn),程序已經(jīng)執(zhí)行到?return ++x;,但還沒執(zhí)行該語句,此刻x=1
繼續(xù)按一下F6,程序執(zhí)行到?++x;,但還沒執(zhí)行該語句,因此此時(shí)的x=2(剛執(zhí)行完return ++x語句的++x,但沒執(zhí)行return)
繼續(xù)按一下F6,此時(shí),我們發(fā)現(xiàn)程序又跳回到?return +xx?這一行,此刻x=3(執(zhí)行了finally語句里的++x)
從上面過程中可以看到,
- 在?try?里 使用?return?還是會(huì)執(zhí)行finally語句的(我們用debug的模式看到了程序會(huì)條件 finally語句里執(zhí)行)
- 執(zhí)行完finally語句才執(zhí)行 return。為什么?從上面的圖可以合理推理出return +xx;是分開來執(zhí)行的,先執(zhí)行++x,再執(zhí)行finally,最后才執(zhí)行return跳出函數(shù)。因?yàn)槌绦蛘{(diào)兩次跳到了 return +xx; 語句上。(其實(shí)要驗(yàn)證?return ++x?是分開兩部分執(zhí)行的方法很簡單,把變量x變成static變量并在main函數(shù)里輸出,會(huì)發(fā)現(xiàn)x的值還是3,即使兩次跳到?return ++x?也只是第一次執(zhí)行了加1操作,第二次只是執(zhí)行了return而沒有執(zhí)行++x。這里是合理推理,后面有真憑實(shí)據(jù)~~)
看到這,我們可能會(huì)再次糾結(jié)起來了。從上面的驗(yàn)證可以看出,finally語句執(zhí)行了,而且x的值也確實(shí)加到3了,那么為什么y是2呢?
驗(yàn)證為什么是2不是3
翻看官方的jvm規(guī)范就會(huì)把一切的謎團(tuán)解開了:
If the try clause executes a return, the compiled code does the following:
簡單翻譯下:
如果try語句里有return,那么代碼的行為如下:
1.如果有返回值,就把返回值保存到局部變量中
2.執(zhí)行jsr指令跳到finally語句里執(zhí)行
3.執(zhí)行完finally語句后,返回之前保存在局部變量表里的值
根據(jù)上面的說明就可以輕易地解釋為什么是2了。
當(dāng)執(zhí)行到return ++x;時(shí),jvm在執(zhí)行完++x后會(huì)在局部變量表里另外分配一個(gè)空間來保存當(dāng)前x的值。
注意,現(xiàn)在還沒把值返回給y,而是繼續(xù)執(zhí)行finally語句里的語句。等執(zhí)行完后再把之前保存的值(是2不是x)返回給y。
所以就有了y是2不是3的情況。
其實(shí)這里還有一點(diǎn)要注意的是,如果你在finally里也用了return語句,比如return +xx。那么y會(huì)是3。因?yàn)橐?guī)范規(guī)定了,當(dāng)try和finally里都有return時(shí),會(huì)忽略try的return,而使用finally的return。
查看Test.class的字節(jié)碼我們同樣也可以很輕松地知道為什么是2而不是3:
大概講講指令操作順序:
iconst_1: 把常數(shù)1進(jìn)棧 ---> istore_1: 棧頂元素出棧并把元素保存在本地變量表的第二個(gè)位置里(下標(biāo)為1的位置里) ---> iinc 1, 1 : 本地變量表的第二個(gè)元素自增1 --->iload_1:第二個(gè)元素進(jìn)棧 ---> istore_2:棧頂元素出棧并把元素保存在本地變量表的第2個(gè)位置里 ---> iinc 1, 1 : 本地變量表的第二個(gè)元素自增1 ---> iload_2:第二個(gè)元素進(jìn)棧 (注意,此時(shí)棧頂元素為2)---> ireturn:返回棧頂元素。
后面的指令是要在2-7行出現(xiàn)異常時(shí)在跳到12行的,這個(gè)例子沒出現(xiàn)異常,不用關(guān)注。
上面流程棧和本地變量表的情況如下圖:
總結(jié)
- 再次發(fā)現(xiàn)幫助別人解決問題的好處,不僅能幫人還能完善自己
- 字節(jié)碼的知識還是挺實(shí)用的,有空要深入研究下
- 再次證明官方教程和資料真的很有用
- 本文轉(zhuǎn)自:http://www.cnblogs.com/averey/p/4379646.html
轉(zhuǎn)載于:https://www.cnblogs.com/Rozdy/p/4397481.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的你真的了解try{ return }finally{}中的return?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓开发学习日记 DAY5——监听事件o
- 下一篇: 电视剧中提到的 自然的 聊天对话 细节