刷java面試題偶然看到這類問題(try/finally中含有return時的執行順序),覺得挺有意思於是小小的研究了一下,希望經過我添油加醋天馬行空之後,能給你帶來一定的幫助
原題
try {} 裡有一個return語句,那麼緊跟在這個try後的finally {}裡的程式碼會不會被執行?什麼時候被執行?在return前還是後?
乍一看題目很簡單嘛,java規範都說了,finally會在try程式碼塊的return之前執行,你這文章寫得沒意義,不看了
你等等!(拿起我身邊的五尺砍刀)
神奇栗子
看完這個栗子,你在想想執行順序到底是怎樣的
栗子程式碼
public static void main(String[] args) {
int result = test();
System.out.println(result);
}
public static int test() {
int t = 0;
try {
return t;
} finally {
++t;
}
}複製程式碼
分析一下
test()方法內,在try中return了t,那麼在main方法中test()函式的返回值應該是t=0,即控制檯輸出0
但是因為有finally的存在,而finally中對t進行了自增運算,並且finally會在try中的return語句之前執行,所以正確的情況是控制檯輸出1
所以你最終確定的答案是:控制檯輸出1
然而事實並非如此,將程式跑起來之後,得到的結果是:
輸出0
將栗子跑起來親眼看一下吧~
得到這個結果你也許要爆炸了,啥?java規範說的都是錯的?!
不用急,到我給sun洗地的時間了
洗地時間
在洗地之前,你很有必要先理解java中的值傳遞,如果你已經瞭解該內容可略過下面這一個小節點
java中的值傳遞
由於這只是本文內容引申出去的知識點,不過多贅述,隨便嘮兩句,能借此明白則好,不明白希望藉助搜尋引擎明白一下!
在java的方法呼叫中,時常需要傳遞引數,那麼傳遞的引數是將之前的變數直接傳遞給方法內了嗎?
顯然不是的,呼叫方法傳遞引數的時候,傳遞的只是原變數的一個副本(複製體),換句話說就是,將變數的值傳遞給了方法體,而並沒有真正的將變數傳遞進去。
看個栗子:
public static void main(String[] args) {
int t = 0;
test(t);
System.out.println(t);
}
public static int test(int a) {
a = 111;
}複製程式碼
正確輸出是0,因為test()方法內拿到的a,只是t的一個副本(複製體)而並不直接是t,test()內改變了a的值,並不影響t的值
以上是對於基本資料型別,如果對於物件呢?
如果引數是物件,那麼傳遞的是物件的引用的副本(複製體),這也就是為什麼在方法體內對物件進行修改,會真正的改變物件。因為方法體外的引用和方法體內的引用指向的是堆記憶體中的同一個物件,傳遞的是物件的引用
如果這裡還不能理解值傳遞,建議先理解一下這一個概念再繼續往下看
真的開始分析了
為了你看著方便,栗子程式碼再來一份:(我真的不是為了湊字數)
public static void main(String[] args) {
int result = test();
System.out.println(result);
}
public static int test() {
int t = 0;
try {
return t;
} finally {
++t;
}
}複製程式碼
- 當程式碼執行到
return t;
時,並不是直接將t返回了出去,而是將t保留了起來(因為還有一個finally語句塊沒有執行!)
並且這個保留,就是值傳遞性質的一個保留,也就是保留的是t的一個副本(複製體),我這裡先叫他tt吧(不是套套!!)- 接下來執行finally語句塊,finally中將t做了自增運算,t的確變成了1,但是這並沒有影響t的複製體tt的值!保留起來的tt值還是0!
- 這個時候執行完了finally,正式將保留起來的tt返回出去,於是,整個函式的返回結果就是0
- 這個t的副本(複製體)保留的地方是哪兒呢?我查了半天,有個應該靠譜的說法,保留在函式棧中,但具體保留的區域叫什麼,我也不知道,還請知情大佬指教一下!
上圖或許直觀一點?
叫我一聲靈魂畫師我可敢答應!
那麼如果,這個t是一個物件呢?按照前面說的值傳遞的問題,如果t是一個物件,在finally中對t進行修改,那麼最終返回出去的t所顯示出來的資料,應該是經過修改的。
寫一個Person類來檢驗一下吧
public class Test {
public static void main(String[] args) {
Person result = test();
System.out.println(result.age);
}
public static Person test() {
Person t = new Person();
t.age = 0;
try {
return t;
} finally {
t.age++;
}
}
}
class Person {
int age;
}複製程式碼
這段程式碼輸出的是1,因為Person是一個類,t是一個物件的引用,物件例項儲存在堆記憶體中,t的副本tt也是一個物件的引用,t和tt都指向堆記憶體中的物件例項,那麼不論修改誰,實際上物件例項都被修改了!
看完我這一通胡說八道,你應該瞭解了整個執行流程咯?
那麼繼續開一個引申
又一個小栗子
如果在finally中也有一個return,會發生什麼?
public static void main(String[] args) {
int result = test();
System.out.println(result);
}
public static int test() {
int t = 0;
try {
return t;
} finally {
++t;
return t;
}
}複製程式碼
最終輸出的結果是1
就是說,如果try中有return而finally中也有return,那麼後者將會讓前者失效!
理解
=> try中將t保留了一份副本用於返回出去,到了finally中,又有一個return語句,這時候又要建立一個用於返回的副本,那這個時候就有兩個副本了,到底返回誰呢?取後者!
總結
這一個面試題,看似簡單,卻暗藏殺機啊!
可是說了這麼多,結果就是finally在return之後執行嗎?
非也,你沒看見return沒有真正的執行完就開始執行finally嗎?並且是先執行完了finally,才執行完return,這也就很好理解java規範中的finally在return之前執行了。
不過,按如上情況,這句話應該變成這樣:finally比return先執行完畢。是不是就更容易理解了呢?
也就是說,return先被執行了,執行return的時候發現有finally,於是不能那麼快執行完畢return,先去執行finally,等finally執行完畢之後,return才能執行完畢。
全文下來,真是用我的三寸不爛之舌經過滔滔不絕的輸出連綿不絕的蠱惑開啟了你的新世界大門啊
結語
更多內容歡迎訪問我的主頁或我的部落格
如果我的文章確實有幫助到你,請不要忘了點一下文末的"♡"讓他變成"❤"
作為一直雛雞難免很多地方理解不到位,文中若有錯誤請直(bu)接(yao)指(ma)出(wo)
寫作不易!