java面試題之try中含return語(yǔ)句時(shí)代碼的執(zhí)行順序詳解
前言
最近在刷java面試題偶然看到這類問題(try/finally中含有return時(shí)的執(zhí)行順序),覺得挺有意思于是小小的研究了一下,希望經(jīng)過我添油加醋天馬行空之后,能給你帶來(lái)一定的幫助,下面來(lái)看看詳細(xì)的介紹。
原題
try {} 里有一個(gè)return語(yǔ)句,那么緊跟在這個(gè)try后的finally {}里的代碼會(huì)不會(huì)被執(zhí)行?什么時(shí)候被執(zhí)行?在return前還是后?
乍一看題目很簡(jiǎn)單嘛,java規(guī)范都說了,finally會(huì)在try代碼塊的return之前執(zhí)行,你這文章寫得沒意義,不看了
你等等!(拿起我身邊的五尺砍刀)
神奇栗子
看完這個(gè)栗子,你在想想執(zhí)行順序到底是怎樣的
栗子代碼
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()方法內(nèi),在try中return了t,那么在main方法中test()函數(shù)的返回值應(yīng)該是t=0,即控制臺(tái)輸出0
但是因?yàn)橛衒inally的存在,而finally中對(duì)t進(jìn)行了自增運(yùn)算,并且finally會(huì)在try中的return語(yǔ)句之前執(zhí)行,所以正確的情況是控制臺(tái)輸出1
所以你最終確定的答案是:控制臺(tái)輸出1
然而事實(shí)并非如此,將程序跑起來(lái)之后,得到的結(jié)果是:
輸出0
將栗子跑起來(lái)親眼看一下吧~
得到這個(gè)結(jié)果你也許要爆炸了,啥?java規(guī)范說的都是錯(cuò)的?!
不用急,到我給sun洗地的時(shí)間了
洗地時(shí)間
在洗地之前,你很有必要先理解java中的值傳遞,如果你已經(jīng)了解該內(nèi)容可略過下面這一個(gè)小節(jié)點(diǎn)
java中的值傳遞
由于這只是本文內(nèi)容引申出去的知識(shí)點(diǎn),不過多贅述,隨便嘮兩句,能借此明白則好,不明白希望借助搜索引擎明白一下!
在java的方法調(diào)用中,時(shí)常需要傳遞參數(shù),那么傳遞的參數(shù)是將之前的變量直接傳遞給方法內(nèi)了嗎?
顯然不是的,調(diào)用方法傳遞參數(shù)的時(shí)候,傳遞的只是原變量的一個(gè)副本(復(fù)制體),換句話說就是,將變量的值傳遞給了方法體,而并沒有真正的將變量傳遞進(jìn)去。
看個(gè)栗子:
public static void main(String[] args) {
int t = 0;
test(t);
System.out.println(t);
}
public static int test(int a) {
a = 111;
}
正確輸出是0,因?yàn)?code>test()方法內(nèi)拿到的a,只是t的一個(gè)副本(復(fù)制體)而并不直接是t,test()內(nèi)改變了a的值,并不影響t的值
以上是對(duì)于基本數(shù)據(jù)類型,如果對(duì)于對(duì)象呢?
如果參數(shù)是對(duì)象,那么傳遞的是對(duì)象的引用的副本(復(fù)制體),這也就是為什么在方法體內(nèi)對(duì)對(duì)象進(jìn)行修改,會(huì)真正的改變對(duì)象。因?yàn)榉椒w外的引用和方法體內(nèi)的引用指向的是堆內(nèi)存中的同一個(gè)對(duì)象,傳遞的是對(duì)象的引用
如果這里還不能理解值傳遞,建議先理解一下這一個(gè)概念再繼續(xù)往下看
真的開始分析了
為了你看著方便,栗子代碼再來(lái)一份:(我真的不是為了湊字?jǐn)?shù))
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;
}
}
- 當(dāng)代碼執(zhí)行到return t;時(shí),并不是直接將t返回了出去,而是將t保留了起來(lái)(因?yàn)檫€有一個(gè)finally語(yǔ)句塊沒有執(zhí)行!)并且這個(gè)保留,就是值傳遞性質(zhì)的一個(gè)保留,也就是保留的是t的一個(gè)副本(復(fù)制體),我這里先叫他tt吧(不是套套??!)
- 接下來(lái)執(zhí)行finally語(yǔ)句塊,finally中將t做了自增運(yùn)算,t的確變成了1,但是這并沒有影響t的復(fù)制體tt的值!保留起來(lái)的tt值還是0!
- 這個(gè)時(shí)候執(zhí)行完了finally,正式將保留起來(lái)的tt返回出去,于是,整個(gè)函數(shù)的返回結(jié)果就是0
- 這個(gè)t的副本(復(fù)制體)保留的地方是哪兒呢?我查了半天,有個(gè)應(yīng)該靠譜的說法,保留在函數(shù)棧中,但具體保留的區(qū)域叫什么,我也不知道,還請(qǐng)知情大佬指教一下!
上圖或許直觀一點(diǎn)?

那么如果,這個(gè)t是一個(gè)對(duì)象呢?按照前面說的值傳遞的問題,如果t是一個(gè)對(duì)象,在finally中對(duì)t進(jìn)行修改,那么最終返回出去的t所顯示出來(lái)的數(shù)據(jù),應(yīng)該是經(jīng)過修改的。
寫一個(gè)Person類來(lái)檢驗(yàn)一下吧
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,因?yàn)镻erson是一個(gè)類,t是一個(gè)對(duì)象的引用,對(duì)象實(shí)例保存在堆內(nèi)存中,t的副本tt也是一個(gè)對(duì)象的引用,t和tt都指向堆內(nèi)存中的對(duì)象實(shí)例,那么不論修改誰(shuí),實(shí)際上對(duì)象實(shí)例都被修改了!
看完我這一通胡說八道,你應(yīng)該了解了整個(gè)執(zhí)行流程咯?
那么繼續(xù)開一個(gè)引申
又一個(gè)小栗子
如果在finally中也有一個(gè)return,會(huì)發(fā)生什么?
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;
}
}
最終輸出的結(jié)果是1
就是說,如果try中有return而finally中也有return,那么后者將會(huì)讓前者失效!
理解
=> try中將t保留了一份副本用于返回出去,到了finally中,又有一個(gè)return語(yǔ)句,這時(shí)候又要?jiǎng)?chuàng)建一個(gè)用于返回的副本,那這個(gè)時(shí)候就有兩個(gè)副本了,到底返回誰(shuí)呢?取后者!
總結(jié)
這一個(gè)面試題,看似簡(jiǎn)單,卻暗藏殺機(jī)??!
可是說了這么多,結(jié)果就是finally在return之后執(zhí)行嗎?
非也,你沒看見return沒有真正的執(zhí)行完就開始執(zhí)行finally嗎?并且是先執(zhí)行完了finally,才執(zhí)行完return,這也就很好理解java規(guī)范中的finally在return之前執(zhí)行了。
不過,按如上情況,這句話應(yīng)該變成這樣:finally比return先執(zhí)行完畢。是不是就更容易理解了呢?
也就是說,return先被執(zhí)行了,執(zhí)行return的時(shí)候發(fā)現(xiàn)有finally,于是不能那么快執(zhí)行完畢return,先去執(zhí)行finally,等f(wàn)inally執(zhí)行完畢之后,return才能執(zhí)行完畢。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Java類加載異常:java.lang.ClassNotFoundException解決方法
這篇文章主要給大家介紹了關(guān)于Java類加載異常:java.lang.ClassNotFoundException的解決方法,異常是Java編程語(yǔ)言中的一個(gè)標(biāo)準(zhǔn)異常類,它繼承自類,當(dāng)在運(yùn)行時(shí)嘗試加載類時(shí),如果系統(tǒng)找不到指定的類文件就會(huì)拋出該異常,需要的朋友可以參考下2023-11-11
基于Zookeeper實(shí)現(xiàn)服務(wù)注冊(cè)和服務(wù)發(fā)現(xiàn)功能
無(wú)論是采用SOA還是微服務(wù)架構(gòu),都需要使用服務(wù)注冊(cè)和服務(wù)發(fā)現(xiàn)組件,本文將基于 Zookeeper 實(shí)現(xiàn)服務(wù)注冊(cè)和服務(wù)發(fā)現(xiàn)功能,如果跟我一樣有同樣的困惑,希望可以通過本文了解其他組件如何使用 Zookeeper 作為注冊(cè)中心的工作原理2023-09-09
spring mvc中@PathVariable / 帶斜杠方式獲取
這篇文章主要介紹了spring mvc中@PathVariable / 帶斜杠方式獲取,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java利用redis zset實(shí)現(xiàn)延時(shí)任務(wù)詳解
zset作為redis的有序集合數(shù)據(jù)結(jié)構(gòu)存在,排序的依據(jù)就是score。本文就將利用zset score這個(gè)排序的這個(gè)特性,來(lái)實(shí)現(xiàn)延時(shí)任務(wù),感興趣的可以了解一下2022-08-08

