非常詳細(xì)講解Java中try, catch and finally相關(guān)解析以及它們與return的執(zhí)行順序

轉(zhuǎn)自:https://blog.csdn.net/S_gy_Zetrov/article/details/68490882、

Java中關(guān)于try…catch…finally異常處理的細(xì)節(jié)辨析

什么時(shí)候執(zhí)行finally; finally后面的語(yǔ)句執(zhí)行嗎; try…catch…finally塊中的finally語(yǔ)句是不是一定會(huì)被執(zhí)行; Java中finally與return的執(zhí)行順序詳解;

首先需要明確幾點(diǎn):

try語(yǔ)句塊中的代碼應(yīng)是可能出現(xiàn)異常的代碼,可能會(huì)拋出一個(gè)或多個(gè)異常,因此,try后面可跟一個(gè)或多個(gè)catch
如果異常間關(guān)系不大,則catch的順序可以隨意。但如果異常間有父類子類繼承關(guān)系,則必須將子類異常catch放置在父類異常catch前面,以防止一場(chǎng)直接被父類catch接住,沒有輸出我們想要的具體子類異常信息
如果try中沒有出現(xiàn)異常,則catch不被執(zhí)行。但不管catch接沒接住,try有沒有異常,只要有finally,則都會(huì)執(zhí)行finally(general情況,其他情況詳見后)。
finally后面的語(yǔ)句是否執(zhí)行與try或catch中有無return有關(guān)
這篇博客耗的時(shí)間比較長(zhǎng),我仔細(xì)驗(yàn)證了幾個(gè)猜測(cè)。現(xiàn)在記錄下來。

整篇文章較長(zhǎng),閱讀大約需要8min

finally后面的語(yǔ)句什么時(shí)候執(zhí)行?

還是先看一個(gè)例子:

public class TestFinally {
    public static void main(String[] args){
        try{
            return;
        }
        finally{
            System.out.println("Finally");
        }
    }
}

輸出:

Finally

可以看出:當(dāng)try中無異常發(fā)生,finally仍執(zhí)行

如果去除return更換為其它普通語(yǔ)句

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后面語(yǔ)句正常執(zhí)行

如果try中加入return,不能編譯,提示

int b=5;
b+=5;
System.out.println(b);

這部分代碼是unreachable code
得證如果try中有return,則finally后面的代碼不會(huì)執(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不會(huì)執(zhí)行,catch后的代碼正常執(zhí)行

刪去exception000項(xiàng)后

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項(xiàng)后

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后面的代碼不會(huì)執(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í)行,得證。

什么時(shí)候finally執(zhí)行?什么時(shí)候不執(zhí)行?

經(jīng)過搜索,找到一位前輩的博客,她寫到至少兩種情況下finally是出現(xiàn)但不執(zhí)行的

try語(yǔ)句沒有被執(zhí)行到,如在try語(yǔ)句之前就返回了,這樣finally語(yǔ)句就不會(huì)執(zhí)行,這也說明了finally語(yǔ)句被執(zhí)行的必要而非充分條件是:相應(yīng)的try語(yǔ)句一定被執(zhí)行到。
在try塊中有System.exit(0);這樣的語(yǔ)句,System.exit(0);是終止Java虛擬機(jī)JVM的,連JVM都停止了,所有都結(jié)束了,當(dāng)然finally語(yǔ)句也不會(huì)被執(zhí)行到。
接下來的內(nèi)容就比較深入細(xì)節(jié)了,沒有這方面了解的需求可直接略過

現(xiàn)在我們已經(jīng)知道finally后面的語(yǔ)句執(zhí)行與try/catch中有無return有關(guān),但finally塊本身執(zhí)行是在return的前面還是后面還是什么時(shí)候?
一個(gè)我比較認(rèn)同的結(jié)論:

finally塊的語(yǔ)句在try或catch中的return語(yǔ)句執(zhí)行之后返回之前執(zhí)行且finally里的修改語(yǔ)句可能影響也可能不影響try或catch中 return已經(jīng)確定的返回值,若finally里也有return語(yǔ)句則覆蓋try或catch中的return語(yǔ)句直接返回

要想明白這個(gè)問題,需要一些背景知識(shí):

java方法是在棧幀中執(zhí)行,棧幀是線程私有棧的單位,執(zhí)行方法的線程會(huì)為每一個(gè)方法分配一小塊棧空間來作為該方法執(zhí)行時(shí)的內(nèi)存空間,棧幀分為三個(gè)區(qū)域:

操作數(shù)棧,用來保存正在執(zhí)行的表達(dá)式中的操作數(shù),數(shù)據(jù)結(jié)構(gòu)中學(xué)習(xí)過基于棧的多項(xiàng)式求值算法,操作數(shù)棧的作用和這個(gè)一樣
局部變量區(qū),用來保存方法中使用的變量,包括方法參數(shù),方法內(nèi)部聲明的變量,以及方法中使用到的對(duì)象的成員變量或類的成員變量(靜態(tài)變量),最后兩種變量會(huì)復(fù)制到局部變量區(qū),因此在多線程 環(huán)境下,這種變量需要根據(jù)需要聲明為volatile類型
字節(jié)碼指令區(qū),這個(gè)不用解釋了,就是方法中的代碼翻譯成的指令
return語(yǔ)句:
return語(yǔ)句的格式如下:
return [expression];
其中expression(表達(dá)式)是可選的,因?yàn)橛行┓椒]有返回值,所以return后面也就沒有表達(dá)式,或者可以看做是空的表達(dá)式。
我們知道return語(yǔ)句的作用可以結(jié)束方法并返回一個(gè)值,那么他返回的是哪里的值呢?返回的是return指令執(zhí)行的時(shí)刻,操作數(shù)棧頂?shù)闹?,不管expression是一個(gè)怎樣的表達(dá)式,究竟做了些什么工作,對(duì)于return指令來說都不重要,他只負(fù)責(zé)把操作數(shù)棧頂?shù)闹捣祷亍?/p>

而return expression是分成兩部分執(zhí)行的:

  1. 執(zhí)行:expression;
  2. 執(zhí)行:return指令;

例如:return x+y;

這句代碼先執(zhí)行x+y,再執(zhí)行return;首先執(zhí)行將x以及y從局部變量區(qū)復(fù)制到操作數(shù)棧頂?shù)闹噶?,然后?zhí)行加法指令,這個(gè)時(shí)候結(jié)果x+y的值會(huì)保存在操作數(shù)棧的棧頂,最后執(zhí)行return指令,返回操作數(shù)棧頂?shù)闹怠?/p>

對(duì)于return x;先執(zhí)行x,x也是一個(gè)表達(dá)式,這個(gè)表達(dá)式只有一個(gè)操作數(shù),會(huì)執(zhí)行將變量x從局部變量區(qū)復(fù)制到操作數(shù)棧頂?shù)闹噶?,然后?zhí)行return,返回操作數(shù)棧頂?shù)闹怠?/p>

因此return x實(shí)際返回的是return指令執(zhí)行時(shí),x在操作數(shù)棧頂?shù)囊粋€(gè)快照或者叫副本,而不是x這個(gè)值。

finally語(yǔ)句在return語(yǔ)句執(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語(yǔ)句已經(jīng)執(zhí)行了再去執(zhí)行finally語(yǔ)句,不過并沒有直接返回,而是等f(wàn)inally語(yǔ)句執(zhí)行完了再返回結(jié)果。即:finally語(yǔ)句在return語(yǔ)句執(zhí)行之后return返回之前執(zhí)行的。
再來一個(gè)栗子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語(yǔ)句,那么是不是就直接返回了,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中是否還有返回語(yǔ)句,這里還有個(gè)小細(xì)節(jié)需要注意,finally里加上return過后,finally外面的return b就變成不可到達(dá)語(yǔ)句了,也就是永遠(yuǎn)不能被執(zhí)行到,所以需要注釋掉否則編譯器報(bào)錯(cuò)。即finally塊中的return語(yǔ)句會(huì)覆蓋try塊中的return返回。

如果finally中沒有return,會(huì)如何返回?

try{
    return expression;
}finally{
    do some work;
}

首先我們知道,finally語(yǔ)句是一定會(huì)執(zhí)行,但他們的執(zhí)行順序是怎么樣的呢?他們的執(zhí)行順序如下:

1.執(zhí)行:expression,計(jì)算該表達(dá)式,結(jié)果保存在操作數(shù)棧頂;
2.執(zhí)行:操作數(shù)棧頂值(expression的結(jié)果)復(fù)制到局部變量區(qū)作為返回值;
3.執(zhí)行:finally語(yǔ)句塊中的代碼;
4.執(zhí)行:將第2步復(fù)制到局部變量區(qū)的返回值又復(fù)制回操作數(shù)棧頂;
5.執(zhí)行:return指令,返回操作數(shù)棧頂?shù)闹担?/p>

我們可以看到,在第一步執(zhí)行完畢后,整個(gè)方法的返回值就已經(jīng)確定了,由于還要執(zhí)行finally代碼塊,因此程序會(huì)將返回值暫存在局部變量區(qū),騰出操作數(shù)棧用來執(zhí)行finally語(yǔ)句塊中代碼,等f(wàn)inally執(zhí)行完畢,再將暫存的返回值又復(fù)制回操作數(shù)棧頂。所以無論finally語(yǔ)句塊中執(zhí)行了什么操作,都無法影響返回值,所以試圖在finally語(yǔ)句塊中修改返回值是徒勞的。因此,finally語(yǔ)句塊設(shè)計(jì)出來的目的只是為了讓方法執(zhí)行一些重要的收尾工作,而不是用來計(jì)算返回值的。

但我前面剛說finally里的return直接返回了,就不管try中是否還有返回語(yǔ)句,都不管try直接返回了,這是不是矛盾呢?

并不是。仔細(xì)閱讀剛才的代碼,可以看到,執(zhí)行return后面的表達(dá)式,但值并沒有立即返回,會(huì)轉(zhuǎn)去執(zhí)行finally,然后再返回剛才沒有返回的值。如果finally里面返回了一個(gè)常值如return 200,這時(shí)才會(huì)直接返回,不管之前要返回的值。

如果finally里沒有return語(yǔ)句,但修改了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中對(duì)改基本數(shù)據(jù)變量的修改不會(huì)生效!如果作用的是對(duì)象如Integer包裝類或者其他正常對(duì)象如`Map
每次返回的一定是try中的return語(yǔ)句?那么finally后面的return語(yǔ)句(如果有)永遠(yuǎn)不會(huì)執(zhí)行?
try塊里的return語(yǔ)句在異常的情況下不會(huì)被執(zhí)行,這樣具體返回哪個(gè)看情況

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

這里因?yàn)樵趓eturn之前發(fā)生了除0異常,所以try中的return不會(huì)被執(zhí)行到,而是接著執(zhí)行捕獲異常的catch 語(yǔ)句和最終的finally語(yǔ)句,此時(shí)兩者對(duì)b的修改都影響了最終的返回值,這時(shí)return b;就起到作用了。當(dāng)然如果將return b改為return 300什么的,最后返回的就是300。

如果catch中有return,則執(zhí)行情況與未發(fā)生異常時(shí)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語(yǔ)句先執(zhí)行,確定了返回值后再去執(zhí)行finally塊,執(zhí)行完了catch再返回,finally里對(duì)b的改變對(duì)返回值無影響,原因同前面一樣,也就是說情況與try中的return語(yǔ)句執(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,656評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,446評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,951評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,189評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,800評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,420評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,755評(píng)論 2 371

推薦閱讀更多精彩內(nèi)容