常見的一個完整的try catch finally結(jié)構(gòu)語句是這樣的
try{}
catch(){}
finally{}
有一點(diǎn)基礎(chǔ)的讀者都會對finally有一點(diǎn)理解:
- 如果在try塊,catch塊中return茧痒,會執(zhí)行完finally塊語句后再返回
- 如果在try塊,catch塊中拋出了未受檢查的異常,會執(zhí)行完finally塊語句后再拋出異常
基于這兩點(diǎn),如果程序到達(dá)try塊,那么finally塊的代碼則一定會執(zhí)行,所以finally塊常用來安全的清理資源,鎖釋放贤徒。
但是finally還有一些細(xì)節(jié)需要小心芹壕,不然可能會得到你意想不到的結(jié)果
我們先上一段代碼,請讀者猜測命令行輸出的結(jié)果
public class TestClass {
public int m1(){
int x;
try{
x = 1;
throw new Exception();
} catch (Exception e){
x = 2;
return x;
}finally {
x = 3;
}
}
public int[] m2(){
int[] x = new int[2];
try{
x[0] = 1;
throw new Exception();
} catch (Exception e){
x[1] = 2;
return x;
}finally {
x[1] = 3;
}
}
public String m3(){
String x = null;
try{
x = "1";
throw new Exception();
} catch (Exception e){
x = "2";
return x;
}finally {
x += "3";
}
}
public static void main(String[] args) {
TestClass tc = new TestClass();
System.out.println("m1():" + tc.m1());
System.out.println("m2()[1]:" + tc.m2()[1]);
System.out.println("m3():" + tc.m3());
}
}
最后的返回值是
m1():2
m2()[1]:3
m3():2
如果返回值和你預(yù)期的一樣接奈,那么說明你已經(jīng)理解了我將要說的內(nèi)容踢涌,可以不用繼續(xù)往下看了
首先來看第一個方法
public int m1(){
int x;
try{
x = 1;
throw new Exception();
} catch (Exception e){
x = 2;
return x;
}finally {
x = 3;
}
}
或許有的讀者預(yù)期的結(jié)果是
m1():3
不是說finally塊一定會執(zhí)行嗎,為什么x的值沒有發(fā)生變化,finally塊確實(shí)執(zhí)行了,但是為什么x還是等于2
這里就是我今天要說的第一點(diǎn)序宦,當(dāng)try塊或者catch塊中提前返回時,finally塊的語句不會改變return的返回值
具體來看catch塊和finally塊的字節(jié)碼睁壁,不想看字節(jié)碼,想直接看結(jié)果的可以跳過這個部分
字節(jié)碼分析 ↓↓
//catch塊
// x=2
11: iconst_2
12: istore_1
13: iload_1
14: istore_3
//finally塊
// x=3
15: iconst_3
16: istore_1
17: iload_3
//return
18: ireturn
為了便于理解互捌,我先給出局部變量表1和3位置的含義(不準(zhǔn)確堡僻,因?yàn)榫植孔兞勘硎强梢詮?fù)用的,因此可能這個時候代表的是變量x下一個時刻代表y,但這個例子里局部變量表沒有被復(fù)用)
下標(biāo) | 0 | 1 | 2 | 3 |
---|---|---|---|---|
含義 | int x | int returnValue |
字節(jié)碼的部分我簡單解釋一下
第11疫剃、12的意思就是將數(shù)字2存到局部變量表1的位置
第13、14行的意思是從局部變量表中1的位置讀取數(shù)字2硼讽,然后再將數(shù)字2存到局部變量表3的位置
此時局部變量表的數(shù)值為
下標(biāo) | 0 | 1(x) | 2 | 3(returnValue) |
---|---|---|---|---|
數(shù)值 | int 2 | int 2 |
第15巢价、16行的意思就是將數(shù)字3存到局部變量表1的位置
此時局部變量表的數(shù)值為
下標(biāo) | 0 | 1(x) | 2 | 3(returnValue) |
---|---|---|---|---|
數(shù)值 | int 3 | int 2 |
第17、18行的意思就是讀取局部變量表3位置的數(shù)字作為返回值返回
因此這里看出固阁,當(dāng)語句要執(zhí)行finally塊時壤躲,它先把準(zhǔn)備返回的數(shù)值2臨時保存在了3這個位置,當(dāng)執(zhí)行完finally塊之后备燃,回來把3位置的值給返回了
字節(jié)碼分析 ↑↑
我分析一下過程:
- 首先程序捕獲到異常箍铭,進(jìn)入了catch塊
- 執(zhí)行到x=2之后剖毯,想繼續(xù)執(zhí)行return方法直接返回
- 這時候finally語句塊說,你等等,我還有方法沒執(zhí)行亮钦,先不要返回,這時return語句想秉馏,虛擬機(jī)要求我必須執(zhí)行finally渊季,那好吧,我去執(zhí)行finally語句塊测垛,我把準(zhǔn)備返回的值2先存放在returnValue里面,等我執(zhí)行完了再回來返回
- 執(zhí)行完finally塊
- return回來捏膨,把剛才暫放在returnValue的值2取出來返回
由上面可以看出,finally不管執(zhí)行什么語句,到最后return總是返回已經(jīng)提前準(zhǔn)備好的returnValue,finally語句不會改變catch塊中的返回值
接下來我們來看第二個方法的代碼
public int[] m2(){
int[] x = new int[2];
try{
x[0] = 1;
throw new Exception();
} catch (Exception e){
x[1] = 2;
return x;
}finally {
x[1] = 3;
}
}
這里輸出的x[1] 為什么又是3而不是2了呢食侮,不是說finally不會改變return的返回值嗎
m2()[1]:3
這里是我要說的第二點(diǎn),JAVA中只有值傳遞号涯,沒有引用傳遞,對于引用類型(例如數(shù)組,對象)锯七,仍然也是值傳遞链快,只是這個值傳遞的是對象的地址
那我們繼續(xù)按照剛才的思路,當(dāng)return準(zhǔn)備返回時,先去執(zhí)行finally塊語句起胰,并且把準(zhǔn)備返回的值保存在了retunValue里面
這時returnValue保存的是數(shù)組的首地址:0XABCDEF久又,最后返回值0XABCDEF時不變的,但是可以改變對象的狀態(tài)巫延,使數(shù)組int[1]的更改為3。
此時返回的仍然是0XABCDEF地消,但數(shù)組對象的狀態(tài)已經(jīng)被改變了炉峰。
接下來我們來看第三個方法的代碼
public String m3(){
String x = null;
try{
x = "1";
throw new Exception();
} catch (Exception e){
x = "2";
return x;
}finally {
x += "3";
}
}
綜合以上兩點(diǎn),有經(jīng)驗(yàn)的讀者可能已經(jīng)明白了脉执,為什么m3方法返回的是2而不是12
m3():2
這里就是我的說的第三點(diǎn),不可變類型只能改變引用不能改變狀態(tài),所謂不可變類型不僅是用final修飾的類疼阔,還要求類的域是不可變的,對于String類
x += "3";
這一步會被分解為三個操作
- 獲取x的值"1"
- 生成一個新的String對象半夷,對象的值為"1" + "2"即"12"
- 將新的String對象的引用替換x的引用
在這個方法中婆廊,首先是會將x的地址保存到returnValue
而finally的語句x+= "3"會生成一個新的String對象,并將x的引用指向它
最后return返回的returnValue仍然是"2"這個字符串
總結(jié)
最后總結(jié)一下巫橄,當(dāng)在
try{}
catch(){return}
finally{}
或
try{return}
catch(){}
finally{}
的結(jié)構(gòu)中
- finally塊的代碼一定會執(zhí)行
- finally塊的代碼不會改變try塊catch塊的返回值
- 如果try塊catch塊return的是引用類型的地址淘邻,finally塊可以改變返回對象的狀態(tài)