深入剖析java的try…catch…finally語句
一、前言
前些天參加面試的時候有一道題:
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語句