轉(zhuǎn)自:https://blog.csdn.net/S_gy_Zetrov/article/details/68490882桨武、
Java中關(guān)于try…catch…finally異常處理的細(xì)節(jié)辨析
什么時候執(zhí)行finally; finally后面的語句執(zhí)行嗎; try…catch…finally塊中的finally語句是不是一定會被執(zhí)行; Java中finally與return的執(zhí)行順序詳解;
首先需要明確幾點:
try語句塊中的代碼應(yīng)是可能出現(xiàn)異常的代碼粱坤,可能會拋出一個或多個異常氏淑,因此介汹,try后面可跟一個或多個catch
如果異常間關(guān)系不大骂铁,則catch的順序可以隨意睛竣。但如果異常間有父類子類繼承關(guān)系余佛,則必須將子類異常catch放置在父類異常catch前面柠新,以防止一場直接被父類catch接住,沒有輸出我們想要的具體子類異常信息
如果try中沒有出現(xiàn)異常辉巡,則catch不被執(zhí)行恨憎。但不管catch接沒接住,try有沒有異常郊楣,只要有finally憔恳,則都會執(zhí)行finally(general情況,其他情況詳見后)净蚤。
finally后面的語句是否執(zhí)行與try或catch中有無return有關(guān)
這篇博客耗的時間比較長喇嘱,我仔細(xì)驗證了幾個猜測。現(xiàn)在記錄下來塞栅。
整篇文章較長者铜,閱讀大約需要8min
finally后面的語句什么時候執(zhí)行腔丧?
還是先看一個例子:
public class TestFinally {
public static void main(String[] args){
try{
return;
}
finally{
System.out.println("Finally");
}
}
}
輸出:
Finally
可以看出:當(dāng)try中無異常發(fā)生,finally仍執(zhí)行
如果去除return更換為其它普通語句
public class TestFinally {
public static void main(String[] args){
try{
int a=3;
a+=5;
System.out.println(a);
}
finally{
System.out.println("Finally");
}
int b=5;
b+=5;
System.out.println(b);
}
}
輸出:
Finally
可以看出:finally后面語句正常執(zhí)行
如果try中加入return作烟,不能編譯愉粤,提示
int b=5;
b+=5;
System.out.println(b);
這部分代碼是unreachable code
得證如果try中有return,則finally后面的代碼不會執(zhí)行(for ‘catch’ is the same thing)
附上一些證明栗子拿撩,可以跳過
例1
public class TestFinally {
public static void a() throws Exception{
try{
throw new Exception();
}
catch(Exception e){
System.out.println("exception000");
}
finally{
System.out.println("finally111");
}
}
public static void main(String[] args){
try{
a();
}
catch(Exception e){
System.out.println("exception");
}
System.out.println("finished");
}
}
輸出:
exception000
finally111
finished
異常在方法中被接住衣厘,main中catch不會執(zhí)行,catch后的代碼正常執(zhí)行
刪去exception000項后
public class TestFinally {
public static void a() throws Exception{
try{
throw new Exception();
}
/catch(Exception e){
System.out.println("exception000");
}/
finally{
System.out.println("finally111");
}
}
public static void main(String[] args){
try{
a();
}
catch(Exception e){
System.out.println("exception");
}
System.out.println("finished");
}
}
輸出:
finally111
exception
finished
異常方法中沒被接住压恒,但在main中被接住影暴,catch后代碼正常執(zhí)行
刪去finally111項后
package trycatchfinally;
public class TestFinally {
public static void a() throws Exception{
try{
throw new Exception();
}
catch(Exception e){
System.out.println("exception000");
}
/finally{
System.out.println("finally111");
}/
}
public static void main(String[] args){
try{
a();
}
catch(Exception e){
System.out.println("exception");
}
System.out.println("finished");
}
}
輸出:
exception000
finished
finally被注釋,故不執(zhí)行探赫,異常被方法中的catch接住课梳,main中catch后代碼正常執(zhí)行
例2
public class TestFinally {
public static String output="";
public static void a(int i){
try{
if(i==1){
throw new Exception();
}
output+="1";
}
catch(Exception e){
output+="2";
return;
}
finally{output+="3";}
output+="4";
}
public static void main(String[] args){
a(0);
a(1);
System.out.println(TestFinally.output);
}
}
輸出:
13423
可以看出芒帕,catch中有return员咽,則finally后面的代碼不會執(zhí)行
去掉return后
package trycatchfinally;
public class TestFinally {
public static String output="";
public static void a(int i){
try{
if(i==1){
throw new Exception();
}
output+="1";
}
catch(Exception e){
output+="2";
//return;
}
finally{output+="3";}
output+="4";
}
public static void main(String[] args){
a(0);
a(1);
System.out.println(TestFinally.output);
}
}
輸出:
1234234
finally后代碼執(zhí)行惹骂,得證。
什么時候finally執(zhí)行毛仪?什么時候不執(zhí)行搁嗓?
經(jīng)過搜索,找到一位前輩的博客箱靴,她寫到至少兩種情況下finally是出現(xiàn)但不執(zhí)行的
try語句沒有被執(zhí)行到腺逛,如在try語句之前就返回了,這樣finally語句就不會執(zhí)行衡怀,這也說明了finally語句被執(zhí)行的必要而非充分條件是:相應(yīng)的try語句一定被執(zhí)行到屉来。
在try塊中有System.exit(0);這樣的語句,System.exit(0);是終止Java虛擬機(jī)JVM的狈癞,連JVM都停止了茄靠,所有都結(jié)束了,當(dāng)然finally語句也不會被執(zhí)行到蝶桶。
接下來的內(nèi)容就比較深入細(xì)節(jié)了慨绳,沒有這方面了解的需求可直接略過
現(xiàn)在我們已經(jīng)知道finally后面的語句執(zhí)行與try/catch中有無return有關(guān),但finally塊本身執(zhí)行是在return的前面還是后面還是什么時候真竖?
一個我比較認(rèn)同的結(jié)論:
finally塊的語句在try或catch中的return語句執(zhí)行之后返回之前執(zhí)行且finally里的修改語句可能影響也可能不影響try或catch中 return已經(jīng)確定的返回值脐雪,若finally里也有return語句則覆蓋try或catch中的return語句直接返回
要想明白這個問題,需要一些背景知識:
java方法是在棧幀中執(zhí)行恢共,棧幀是線程私有棧的單位战秋,執(zhí)行方法的線程會為每一個方法分配一小塊棧空間來作為該方法執(zhí)行時的內(nèi)存空間讨韭,棧幀分為三個區(qū)域:
操作數(shù)棧脂信,用來保存正在執(zhí)行的表達(dá)式中的操作數(shù)癣蟋,數(shù)據(jù)結(jié)構(gòu)中學(xué)習(xí)過基于棧的多項式求值算法,操作數(shù)棧的作用和這個一樣
局部變量區(qū)狰闪,用來保存方法中使用的變量疯搅,包括方法參數(shù),方法內(nèi)部聲明的變量埋泵,以及方法中使用到的對象的成員變量或類的成員變量(靜態(tài)變量)幔欧,最后兩種變量會復(fù)制到局部變量區(qū),因此在多線程 環(huán)境下丽声,這種變量需要根據(jù)需要聲明為volatile類型
字節(jié)碼指令區(qū)礁蔗,這個不用解釋了,就是方法中的代碼翻譯成的指令
return語句:
return語句的格式如下:
return [expression]雁社;
其中expression(表達(dá)式)是可選的浴井,因為有些方法沒有返回值,所以return后面也就沒有表達(dá)式歧胁,或者可以看做是空的表達(dá)式滋饲。
我們知道return語句的作用可以結(jié)束方法并返回一個值厉碟,那么他返回的是哪里的值呢喊巍?返回的是return指令執(zhí)行的時刻,操作數(shù)棧頂?shù)闹倒抗模还躤xpression是一個怎樣的表達(dá)式崭参,究竟做了些什么工作,對于return指令來說都不重要款咖,他只負(fù)責(zé)把操作數(shù)棧頂?shù)闹捣祷亍?/p>
而return expression是分成兩部分執(zhí)行的:
- 執(zhí)行:expression何暮;
- 執(zhí)行:return指令;
例如:return x+y铐殃;
這句代碼先執(zhí)行x+y海洼,再執(zhí)行return;首先執(zhí)行將x以及y從局部變量區(qū)復(fù)制到操作數(shù)棧頂?shù)闹噶罡焕埃缓髨?zhí)行加法指令坏逢,這個時候結(jié)果x+y的值會保存在操作數(shù)棧的棧頂,最后執(zhí)行return指令赘被,返回操作數(shù)棧頂?shù)闹怠?/p>
對于return x是整;先執(zhí)行x,x也是一個表達(dá)式民假,這個表達(dá)式只有一個操作數(shù)浮入,會執(zhí)行將變量x從局部變量區(qū)復(fù)制到操作數(shù)棧頂?shù)闹噶睿缓髨?zhí)行return羊异,返回操作數(shù)棧頂?shù)闹怠?/p>
因此return x實際返回的是return指令執(zhí)行時事秀,x在操作數(shù)棧頂?shù)囊粋€快照或者叫副本彤断,而不是x這個值。
finally語句在return語句執(zhí)行之后還是之前執(zhí)行秽晚?
栗子1:
public class TestFinally {
public static void main(String[] args) {
System.out.println(test1());
}
public static int test1() {
int b = 20;
try {
System.out.println("try");
return b += 80;
}
catch (Exception e) {
System.out.println("catch");
}
finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
}
return b;
}
}
輸出:
try
finally
b>25, b = 100
100
說明return語句已經(jīng)執(zhí)行了再去執(zhí)行finally語句瓦糟,不過并沒有直接返回,而是等finally語句執(zhí)行完了再返回結(jié)果赴蝇。即:finally語句在return語句執(zhí)行之后return返回之前執(zhí)行的菩浙。
再來一個栗子2:
public class TestFinally {
public static void main(String[] args) {
System.out.println(test11());
}
public static String test11() {
try {
System.out.println("try");
return test12();
} finally {
System.out.println("finally");
}
}
public static String test12() {
System.out.println("return statement");
return "after return";
}
}
輸出:
try
return statement
finally
after return
如果finally里也有return語句,那么是不是就直接返回了句伶,try中的return就不能返回了劲蜻?
public class TestFinally {
public static void main(String[] args) {
System.out.println(test2());
}
public static int test2() {
int b = 20;
try {
System.out.println("try");
return b += 80;
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
return 200;
}
// return b;
}
}
輸出:
try
finally
b>25, b = 100
200
說明finally里的return直接返回了,就不管try中是否還有返回語句考余,這里還有個小細(xì)節(jié)需要注意先嬉,finally里加上return過后,finally外面的return b就變成不可到達(dá)語句了楚堤,也就是永遠(yuǎn)不能被執(zhí)行到疫蔓,所以需要注釋掉否則編譯器報錯。即finally塊中的return語句會覆蓋try塊中的return返回身冬。
如果finally中沒有return衅胀,會如何返回?
try{
return expression;
}finally{
do some work;
}
首先我們知道酥筝,finally語句是一定會執(zhí)行滚躯,但他們的執(zhí)行順序是怎么樣的呢?他們的執(zhí)行順序如下:
1.執(zhí)行:expression嘿歌,計算該表達(dá)式掸掏,結(jié)果保存在操作數(shù)棧頂;
2.執(zhí)行:操作數(shù)棧頂值(expression的結(jié)果)復(fù)制到局部變量區(qū)作為返回值宙帝;
3.執(zhí)行:finally語句塊中的代碼丧凤;
4.執(zhí)行:將第2步復(fù)制到局部變量區(qū)的返回值又復(fù)制回操作數(shù)棧頂;
5.執(zhí)行:return指令步脓,返回操作數(shù)棧頂?shù)闹担?/p>
我們可以看到愿待,在第一步執(zhí)行完畢后,整個方法的返回值就已經(jīng)確定了沪编,由于還要執(zhí)行finally代碼塊呼盆,因此程序會將返回值暫存在局部變量區(qū),騰出操作數(shù)棧用來執(zhí)行finally語句塊中代碼蚁廓,等finally執(zhí)行完畢访圃,再將暫存的返回值又復(fù)制回操作數(shù)棧頂。所以無論finally語句塊中執(zhí)行了什么操作相嵌,都無法影響返回值腿时,所以試圖在finally語句塊中修改返回值是徒勞的况脆。因此,finally語句塊設(shè)計出來的目的只是為了讓方法執(zhí)行一些重要的收尾工作批糟,而不是用來計算返回值的格了。
但我前面剛說finally里的return直接返回了,就不管try中是否還有返回語句徽鼎,都不管try直接返回了盛末,這是不是矛盾呢?
并不是否淤。仔細(xì)閱讀剛才的代碼悄但,可以看到,執(zhí)行return后面的表達(dá)式石抡,但值并沒有立即返回檐嚣,會轉(zhuǎn)去執(zhí)行finally,然后再返回剛才沒有返回的值啰扛。如果finally里面返回了一個常值如return 200嚎京,這時才會直接返回,不管之前要返回的值隐解。
如果finally里沒有return語句鞍帝,但修改了b的值,那么try中return返回的是修改后的值還是原值厢漩?
public class TestFinally {
public static void main(String[] args) {
System.out.println(test3());
}
public static int test3() {
int b = 20;
try {
System.out.println("try");
return b += 80;
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b = 150;
}
return 2000;
}
}
輸出:
try
finally
b>25, b = 100
100
如果return使用在基本數(shù)據(jù)變量上膜眠,則finally中對改基本數(shù)據(jù)變量的修改不會生效岩臣!如果作用的是對象如Integer包裝類
或者其他正常對象如`Map
每次返回的一定是try中的return語句溜嗜?那么finally后面的return語句(如果有)永遠(yuǎn)不會執(zhí)行?
try塊里的return語句在異常的情況下不會被執(zhí)行架谎,這樣具體返回哪個看情況
public class TestFinally {
public static void main(String[] args) {
System.out.println(test4());
}
public static int test4() {
int b = 20;
try {
System.out.println("try");
b = b / 0;
return b += 80;
} catch (Exception e) {
b += 15;
System.out.println("catch");
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
}
return 26;
}
}
輸出:
try
catch
finally
b>25, b = 35
26
這里因為在return之前發(fā)生了除0異常炸宵,所以try中的return不會被執(zhí)行到,而是接著執(zhí)行捕獲異常的catch 語句和最終的finally語句谷扣,此時兩者對b的修改都影響了最終的返回值土全,這時return b;就起到作用了。當(dāng)然如果將return b改為return 300什么的会涎,最后返回的就是300裹匙。
如果catch中有return,則執(zhí)行情況與未發(fā)生異常時try中return的執(zhí)行情況完全一樣末秃。
public class TestFinally {
public static void main(String[] args) {
System.out.println(test5());
}
public static int test5() {
int b = 20;
try {
System.out.println("try");
b = b /0;
return b += 80;
} catch (Exception e) {
System.out.println("catch");
return b += 15;
} finally {
System.out.println("finally");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
}
//return b;
//Unreachable code
}
}
輸出:
try
catch
finally
b>25, b = 35
35
說明了發(fā)生異常后概页,catch中的return語句先執(zhí)行,確定了返回值后再去執(zhí)行finally塊练慕,執(zhí)行完了catch再返回惰匙,finally里對b的改變對返回值無影響技掏,原因同前面一樣,也就是說情況與try中的return語句執(zhí)行完全一樣项鬼。
如果想輸出85哑梳,需要這么改,即finally中return绘盟,覆蓋catch中的try鸠真。
public class TestFinally {
public static void main(String[] args) {
System.out.println(test5());
}
public static int test5() {
int b = 20;
try {
System.out.println("try block");
b = b /0;
return b += 80;
} catch (Exception e) {
System.out.println("catch block");
return b += 15;
} finally {
System.out.println("finally block");
if (b > 25) {
System.out.println("b>25, b = " + b);
}
b += 50;
return b;
}
//return b;
}
}
輸出:
try block
catch block
finally block
b>25, b = 35
85
至此,此篇結(jié)束龄毡。關(guān)于這塊的問題相信我已經(jīng)明白了二三事弧哎,希望看到這的你也有收獲 :)
參考文獻(xiàn):
1:http://www.cnblogs.com/lanxuezaipiao/p/3440471.html
2:http://blog.csdn.net/qj19842011/article/details/45675057