1. 程式人生 > >深入剖析java的try…catch…finally語句

深入剖析java的try…catch…finally語句

ppr ive after 內容 我們 title oid stat blog

一、前言

前些天參加面試的時候有一道題:

public class test {
    public static void main(String[] args){
        try {
            return;
        } 
        finally{
            System.out.println("finally...");
        }
    }
}
以上程序的執行結果是什麽?

當時覺得finally塊肯定會被執行到的,而這段程序在try塊裏就已經返回了,所以選了“編譯出現錯誤”這個選項,回來之後驗證了一下,結果是輸出“finally…”,越發覺得這個問題很有趣。

二、剖析

1. 從字節碼分析

為了更好的說明問題,選用一篇博客裏的例子[1]進行說明,源碼:

public class Test {
    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";
 
        try {
            t = "try";
            return t;
        } catch (Exception e) {
            t = "catch";
            
return t; } finally { t = "finally"; } } public static void main(String[] args) { System.out.print(Test.test()); } }

按照一般的思路,首先程序執行try語句塊,把變量t賦值為try,由於沒有發現異常,接下來執行finally語句塊,把變量t賦值為finally,然後return t,則t的值是finally,最後t的值就是finally,程序結果應該顯示finally,但是實際結果為try。為什麽會這樣,我們不妨先看看這段代碼編譯出來的class對應的字節碼,看虛擬機內部是如何執行的。

我們用javap -verbose Test 來顯示目標文件(.class文件)字節碼信息。

系統運行環境:win7 64位

jdk信息:java version "1.8.0_05",Java(TM) SE Runtime Environment (build 1.8.0_05-b13),Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)

編譯出來的字節碼部分信息,我們只看test方法,其他的先忽略掉:

public static final java.lang.String test();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=1, locals=4, args_size=0
         0: ldc           #2                  // String 
         2: astore_0      
         3: ldc           #3                  // String try
         5: astore_0      
         6: aload_0       
         7: astore_1      
         8: ldc           #4                  // String finally
        10: astore_0      
        11: aload_1       
        12: areturn       
        13: astore_1      
        14: ldc           #6                  // String catch
        16: astore_0      
        17: aload_0       
        18: astore_2      
        19: ldc           #4                  // String finally
        21: astore_0      
        22: aload_2       
        23: areturn       
        24: astore_3      
        25: ldc           #4                  // String finally
        27: astore_0      
        28: aload_3       
        29: athrow        
      Exception table:
         from    to  target type
             3     8    13   Class java/lang/Exception
             3     8    24   any
            13    19    24   any
      LineNumberTable:
        line 5: 0
        line 8: 3
        line 9: 6
        line 14: 8
        line 10: 13
        line 11: 14
        line 12: 17
        line 14: 19
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 13
          locals = [ class java/lang/String ]
          stack = [ class java/lang/Exception ]
           frame_type = 74 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]

觀察Code部分:

第[0-2]行,給第0個變量賦值“”,也就是String t="";

第[3-5]行,也就是執行try語句塊 賦值語句 ,也就是 t = "try";

第[6-7]行,重點是第7行,把第t對應的值"try"賦給第1個變量,但是這裏面第1個變量並沒有定義這個比較奇怪;

第[8-10] 行,對第0個變量進行賦值操作,也就是t="finally";

第[11-12]行,把第1個變量對應的值返回;

通過字節碼,我們知道,在return之前,虛擬機會創建一個中間變量,我們暫時可以稱為t’,然後把t的值賦值給t’,接下來去執行finally塊的內容,最後返回t’,所以即使在finally塊裏修改了t,但是return返回的是t’,所以最後輸出的是t’的內容,如下圖所示:

技術分享圖片

2. 如果try和finally塊裏面修改的是可變的對象

class myObject{
    private int value = 0;

    public myObject(int value){
        this.value = value;
    }
    
    public void setValue(int value){
        this.value = value;
    }

    public void print(){
        System.out.println("obj:" + this + ",value:" +  value);
    }

}

public class Test {
    @SuppressWarnings("finally")
    public static final myObject test() {
        myObject myObj = null;
 
        try {
            myObj = new myObject(1);
            System.out.println("in try block");
            myObj.print();
            return myObj;
        } catch (Exception e) {
            myObj.setValue(2);
            return myObj;
        } finally {
            myObj.setValue(3);
        }
    }
 
    public static void main(String[] args) {
        myObject newObj = test();
        System.out.println("after return");
        newObj.print();
    }
}

輸出:

in try block
obj:myObject@15db9742,value:1
after return
obj:myObject@15db9742,value:3

在這個例子中,即使myObj會賦值給myObj’,然而他們都指向同一個對象,因此在finally塊中對這個對象的修改當然反映到myObj’中。

3. try和finally塊中都有return語句

還是用String的例子,不過finally語句增加了return語句

public class Test {
    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";
 
        try {
            t = "try";
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
return t;
        }
    }
 
    public static void main(String[] args) {
        System.out.print(Test.test());
    }
}

最後輸出:

finally

可見這種情況,執行完finally之後就直接返回了。

4. catch語句被執行的情況並且有return語句

public class Test {
    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";
 
        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
        }
    }
 
    public static void main(String[] args) {
        System.out.print(Test.test());
    }
}

最後輸出:

catch

可見try和catch語句裏都存在使用中間變量的情況。

5. catch塊中拋出異常的情況

public class Test {
    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";
 
        try {
            t = "try";
            Integer.parseInt(null);
            return t;
        } catch (Exception e) {
            t = "catch";
            Integer.parseInt(null);
            return t;
        } finally {
            t = "finally";
        }
    }
 
    public static void main(String[] args) {
        System.out.print(Test.test());
    }
}

輸出:

Exception in thread "main" java.lang.NumberFormatException: null
    at java.lang.Integer.parseInt(Integer.java:542)
    at java.lang.Integer.parseInt(Integer.java:615)
    at Test.test(Test.java:12)
    at Test.main(Test.java:20)

執行過程大概是,執行try塊,Integer.parseInt(null)語句拋出異常,進入catch語句,然後又拋出異常,然後執行finally塊,對t進行賦值,因為finally沒有返回,所以執行完之後,catch把異常拋出。

如果finally塊中有return語句呢,在finally塊最後加多一條語句”return t;”,最後輸出:

finally

6. finally塊中拋出異常

public class Test {
    @SuppressWarnings("finally")
    public static final String test() {
        String t = "";
 
        try {
            t = "try";
            return t;
        } catch (Exception e) {
            t = "catch";
            return t;
        } finally {
            t = "finally";
            String.valueOf(null);
            return t;
        }
    }
 
    public static void main(String[] args) {
        System.out.print(Test.test());
    }
}

輸出:

Exception in thread "main" java.lang.NullPointerException[Finished in 1.1s with exit code 1]
    at java.lang.String.<init>(String.java:166)
    at java.lang.String.valueOf(String.java:2993)
    at Test.test(Test.java:14)
    at Test.main(Test.java:20)

可見執行到finally塊,產生異常之後會終止當前的其他操作,向上拋出異常。

三、小結

  • try,catch,finally語句中,如果在try/catch塊中存在return語句,finally塊沒有return語句,try/catch塊會產生一個臨時變量(t’)存儲return 語句中的變量(t),如果這個變量類型是值類型或者不可變對象,則在finally塊中對變量t的修改不會影響到try/catch中返回的結果;如果是可變對象類型,則結果會影響;
  • 如果finally塊中有return語句,則try和catch中的return語句都會忽略;
  • 如果finally塊中拋出異常,則停止try…catch…finally中的其他操作,直接向上拋出異常。

四、參考

1. java中關於try、catch、finally中的細節分析

2. 從Java代碼到字節碼(1)

深入剖析java的try…catch…finally語句