Java 異常處理try、catch、finally和return執行順序


問題背景

finally語句一定執行嗎

   網上有很多人探討Java中異常捕獲機制 try...catch...finally 塊中的finally語句是不是一定會被執行?很多人都說不是,當然他們的回答是正確的,至少有兩種情況下finally語句是不會被執行的:

   (1)try語句沒有被執行到就不會執行finally。如在try語句之前程序結束執行,那么finally語句就不會執行,這也說明了finally語句被執行的必要而非充分條件是相應的try語句一定被執行到

   (2)在try塊中執行了System.exit(0)。System.exit(0)的作用是終止Java虛擬機,連JVM都停止了,所有都結束了,當然finally語句也不會被執行到。

finally語句與return的執行順序

   還有很多人探討finally語句的執行與return的關系,讓人頗為迷惑,不知道finally語句是在try的return之前執行還是之后執行?我覺得應該是:finally語句是在try的return語句執行之后,return返回之前執行。這樣的說法有點矛盾,也許是我表述不太清楚,下面我給出自己試驗的一些結果和示例進行佐證,有什么問題歡迎大家提出來。

  記return后的語句為y=f(),則finally語句在f()執行之后、y返回之前執行。

問題分析

1. finally語句在f()執行之后、y返回之前執行

public class FinallyTest1 {

    public static void main(String[] args) {
          System.out.println(test1());
    }

    public static String test1() {
        try {
            System.out.println("try block");
             // ①計算 f() 結果,記作 y,② 執行 finally,③ 返回 y
            return test2();
        } finally {
            System.out.println("finally block");
        }
    }

    public static String test2() {
        System.out.println("return statement");
        return "after return";
    }

  運行結果為:

try block
return statement
finally block
after return

  說明try中的f() = test2() 先執行了,得到結果y,並且待finally塊執行結束后再返回y。這里大家可能會想:如果finally里也有return語句,那么是不是就直接返回了,try中的return就不能返回了?看下面。

2. finally塊中的return語句覆蓋try塊中的y

public class FinallyTest2 {
   public static void main(String[] args) {
      System.out.println(test2());
   }
    public static int test2() {
        int b = 20;
        try {
            System.out.println("try block");
            return b += 80;
        } catch (Exception e) {
            System.out.println("catch block");
        } finally {
            System.out.println("finally block");
            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            return 200;//可以被“具體值”覆蓋
        }
        // return b;
    }

運行結果是:

try block
finally block
b>25, b = 100
200

這說明finally里的return直接返回了,就不管try中是否還有返回語句,這也是不建議在finally中使用return的原因。這里還有個小細節需要注意,finally里加上return過后,try...catch...finally 外面的return b就變成不可到達語句了,也就是永遠不能被執行到,所以需要注釋掉;否則,編譯失敗。

那么直接從finally中返回了,這也是不建議在finally中return的原因。

這里大家可能又想:如果finally里沒有return語句,但修改了b的值,那么try中return返回的是修改后的值還是原值?看下面。

3. finally語句塊不改變棧中的值,可改變堆中的值

本節討論如下場景:finally中沒有return語句,但是改變了要返回的值。返回值y的類型如果是基本類型,則在finally塊里被修改也不影響y,如果是引用類型則可以。如果y是文本字符串,也不受影響。

返回基本類型變量時是值,返回引用類型時是指向某個對象的地址;而且基本類型是被分配在棧中的,對象是被分配在堆中的,只要有引用指向這個對象,系統就不會回收此對象,所以可以在后面的finally塊中改變引用指向的對象的內容,卻無法改變try語句中return要返回的值,因為這個值已經與變量y無關了。

測試用例1:

public class FinallyTest3 {
public static void main(String[] args) {
System.out.println(test3());
}
    public static int test3() {
        int b = 20;
        try {
            System.out.println("try block");
            return b += 80;
        } catch (Exception e) {
            System.out.println("catch block");
        } finally {
            System.out.println("finally block");
            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            b = 150; //基本類型的返回值y,不可被修改
            // return 3200; //可以被“具體值”覆蓋
        }
        return 2000;
    }

}

運行結果是:

try block
finally block
b>25, b = 100
100

執行結果說明沒有修改y。這也驗證了finally語句塊不會影響到棧中的值,即在執行finally之前,棧中的臨時值已經確定為100了,執行finally語句將b的值變為150,對結果沒有任何影響,執行完finally后的輸出結果仍為100。

測試用例2:

import java.util.*;

public class FinallyTest6{
public static void main(String[] args) {
System.out.println(getMap().get("KEY").toString());
}
  public static Map<String, String> getMap() {
       Map<String, String> map = new HashMap<String, String>();
       map.put("KEY", "INIT");
       try {
           map.put("KEY", "TRY");
           return map;
       } catch (Exception e) {
           map.put("KEY", "CATCH");
       } finally {
           map.put("KEY", "FINALLY");// 修改y成功
           map = null;
           System.out.println("map是:" + map);
       }
       System.out.println("---- 走不到此處 --------");//  ①
       return map;
   }

運行結果是:

map是:null
FINALLY

為什么案例 test3()中finally里的b = 150並沒有起到作用而案例 getMap()中finally的map.put("KEY", "FINALLY")起了作用,但是map = null卻沒起作用呢?這就是Java到底是傳值還是傳址的問題了,簡單來說就是:Java中只有傳值沒有傳址,這也是為什么map = null這句不起作用。 ①處的代碼未執行說明finally外面的return 這句未執行。

測試用例3:

    public static String testStr() {
        String b = "init";
        try {
            System.out.println("try block");
            return b;
        } catch (Exception e) {
            System.out.println("catch block");
        } finally {
            b = b + "--";
            System.out.println("finally block, b = " + b);
        }
        return b;
    }

執行結果:

try block
finally block, b = init--
init

說明如果y是文本字符串,在finally語句塊中改變它,也影響返回值。

這里大家可能又要想:是不是每次返回的一定是try中的return語句呢?那么finally外的return 語句一點作用沒嗎?請看下面。

4. try塊里的return在異常情況下不執行

public class FinallyTest4 {

public static void main(String[] args) {

System.out.println(test4());
}

   public static int test4() {
        int b = 20;

        try {
            System.out.println("try block");
            b = b / 0;
            return b += 80; // ①
        } catch (Exception e) {
            b += 15;
            System.out.println("catch block, b = " + b);
        } finally {
            System.out.println("finally block");
            if (b > 25) {
                System.out.println("b>25, b = " + b);
            }
            b += 50;
        }
        System.out.println("---- 走此處 --------" + b);
        return 320 + b; // ②
    }

運行結果是:

try block
catch block, b = 35
finally block
b>25, b = 35
---- 走此處 --------85
405

這里因 為在return之前發生了除0異常,所以①不會被執行,而是接着執行捕獲異常的catch 語句和最終的finally語句。此時兩者對b的修改都影響了最終的返回值,從②返回的結果可以看出來。大家可能又有疑問:如果catch中有return語句呢?當然只有在異常的情況下才有可能會執行,那么是在finally之前就返回嗎?看下面。

5. catch和try中的return執行順序相同

public class FinallyTest5 {

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;
    }
}

運行結果如下:

try block
catch block
finally block
b>25, b = 35
35

執行結果說明發生異常后,catch中的return語句先執行,計算完返回值y后將其保存起來,再去執行finally塊;執行完finally塊就把先去保存的y返回,finally里修改b對返回值y無影響,原因同前面的 test3() 。至此,也就推理出結論:catch中的return語句和try中的執行順序完全一樣。

小結

  • finally塊的語句在try或catch中的return語句執行之后返回之前執行。
  • finally語句塊不改變棧中的值y,可改變堆中的值。
    y的類型如果是基本類型,其臨時存放在棧中,則在finally塊里被修改也不影響y,如果是存放在堆中的引用類型則可以。finally塊里改變文本字符串也不影響y。
  • finally里的return語句覆蓋try或catch中的return語句直接返回。

下面看一個甲骨文的面試題:

public class test {
    public static void main(String[] args) {
        try {
            aMethod();
        } catch (Exception e) {
            System.out.println("exception");// ①
        }
        System.out.println("finish");// ②
    }

    public static void aMethod() throws Exception {
        try {
            throw new Exception();
        } finally {
            System.out.println("finally"); // ③
        }
    }
}

面試題分析:首先,進入上述try代碼塊后,不管try是否拋異常,finally塊一定會執行,因此拋異常之前會執行 ③。然后,拋出的異常被調用者main方法捕獲后執行①。最后,執行②。故執行結果如下:

finally
exception
finish

Reference


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM