【Java學習筆記之三十三】詳解Java中try,catch,finally的用法及分析

這一篇咱們將會介紹java中try,catch,finally的用法java

如下先給出try,catch用法:express

try {   //須要被檢測的異常代碼  }
catch(Exception e) { //異常處理,即處理異常代碼 }
finally
{

  //必定會被執行的代碼

}

代碼區若是有錯誤,就會返回所寫異常的處理。編程

首先要清楚,若是沒有try的話,出現異常會致使程序崩潰。而try則能夠保證程序的正常運行下去,好比說:數組

try
{
    int i = 1/0;
}
catch(Exception e)
{
    ........
}

一個計算的話,若是除數爲0,則會報錯,若是沒有try的話,程序直接崩潰。用try的話,則可讓程序運行下去,而且輸出爲何出錯!服務器

try catch 是捕捉try部分的異常,當你沒有trycatch的時候,若是出現異常則程序報錯,加上try,catch,出現異常程序正常運行,只是把錯誤信息存儲到Exception裏,因此catch是用來提取異常信息的,你能夠在catch部分加上一句System.out.println(e.ToString());,若是出現異常能夠把異常打印出來~~框架

 

java的異常處理機制(try…catch…finally)

1 引子

try…catch…finally恐怕是你們再熟悉不過的語句了,並且感受用起來也是很簡單,邏輯上彷佛也是很容易理解。不過,我親自體驗的「教訓」告訴我,這個東西可不是想象中的那麼簡單、聽話。不信?那你看看下面的代碼,「猜猜」它執行後的結果會是什麼?不要日後看答案、也不準執行代碼看真正答案哦。若是你的答案是正確,那麼這篇文章你就不用浪費時間看啦。less

 1 public class TestException
 2 {
 3     public TestException()
 4     {
 5     }
 6     boolean testEx() throws Exception
 7     {
 8         boolean ret = true;
 9         try
10         {
11             ret = testEx1();
12         }
13         catch (Exception e)
14         {
15             System.out.println("testEx, catch exception");
16             ret = false;
17             throw e;
18         }
19         finally
20         {
21             System.out.println("testEx, finally; return value=" + ret);
22             return ret;
23         }
24     }
25     boolean testEx1() throws Exception
26     {
27         boolean ret = true;
28         try
29         {
30             ret = testEx2();
31             if (!ret)
32             {
33                 return false;
34             }
35             System.out.println("testEx1, at the end of try");
36             return ret;
37         }
38         catch (Exception e)
39         {
40             System.out.println("testEx1, catch exception");
41             ret = false;
42             throw e;
43         }
44         finally
45         {
46             System.out.println("testEx1, finally; return value=" + ret);
47             return ret;
48         }
49     }
50     boolean testEx2() throws Exception
51     {
52         boolean ret = true;
53         try
54         {
55             int b = 12;
56             int c;
57             for (int i = 2; i >= -2; i--)
58             {
59                 c = b / i;
60                 System.out.println("i=" + i);
61             }
62             return true;
63         }
64         catch (Exception e)
65         {
66             System.out.println("testEx2, catch exception");
67             ret = false;
68             throw e;
69         }
70         finally
71         {
72             System.out.println("testEx2, finally; return value=" + ret);
73             return ret;
74         }
75     }
76     public static void main(String[] args)
77     {
78         TestException testException1 = new TestException();
79         try
80         {
81             testException1.testEx();
82         }
83         catch (Exception e)
84         {
85             e.printStackTrace();
86         }
87     }
88 }

你的答案是什麼?是下面的答案嗎?jvm

i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, catch exception
testEx1, finally; return value=false
testEx, catch exception
testEx, finally; return value=false

若是你的答案真的如上面所說,那麼你錯啦。^_^,那就建議你仔細看一看這篇文章或者拿上面的代碼按各類不一樣的狀況修改、執行、測試,你會發現有不少事情不是原來想象中的那麼簡單的。
如今公佈正確答案:函數

i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false

2 基礎知識

2.1 相關概念

例外是在程序運行過程當中發生的異常事件,好比除0溢出、數組越界、文件找不到等,這些事件的發生將阻止程序的正常運行。爲了增強程序的魯棒性,程序設計時,必須考慮到可能發生的異常事件並作出相應的處理。C語言中,經過使用if語句來判斷是否出現了例外,同時,調用函數經過被調用函數的返回值感知在被調用函數中產生的例外事件並進行處理。全程變量ErroNo經常用來反映一個異常事件的類型。可是,這種錯誤處理機制會致使很多問題。
Java經過面向對象的方法來處理例外。在一個方法的運行過程當中,若是發生了例外,則這個方法生成表明該例外的一個對象,並把它交給運行時系統,運行時系統尋找相應的代碼來處理這一例外。咱們把生成例外對象並把它提交給運行時系統的過程稱爲拋棄(throw)一個例外。運行時系統在方法的調用棧中查找,從生成例外的方法開始進行回朔,直到找到包含相應例外處理的方法爲止,這一個過程稱爲捕獲(catch)一個例外。 測試

2.2 Throwable類及其子類  

用面向對象的方法處理例外,就必須創建類的層次。類 Throwable位於這一類層次的最頂層,只有它的後代才能夠作爲一個例外被拋棄。圖1表示了例外處理的類層次。
從圖中能夠看出,類Throwable有兩個直接子類:Error和Exception。Error類對象(如動態鏈接錯誤等),由Java虛擬機生成並拋棄(一般,Java程序不對這類例外進行處理);Exception類對象是Java程序處理或拋棄的對象。它有各類不一樣的子類分別對應於不一樣類型的例外。其中類RuntimeException表明運行時由Java虛擬機生成的例外,如算術運算例外ArithmeticException(由除0錯等致使)、數組越界例外ArrayIndexOutOfBoundsException等;其它則爲非運行時例外,如輸入輸出例外IOException等。Java編譯器要求Java程序必須捕獲或聲明全部的非運行時例外,但對運行時例外能夠不作處理。

2.3 異常處理關鍵字

Java的異常處理是經過5個關鍵字來實現的:try,catch,throw,throws,finally。JB的在線幫助中對這幾個關鍵字是這樣解釋的:
Throws:  Lists the exceptions a method could throw.
Throw:   Transfers control of the method to the exception handler.
Try:    Opening exception-handling statement.
Catch:  Captures the exception.
Finally: Runs its code before terminating the program.

2.3.1 try語句 

try語句用大括號{}指定了一段代碼,該段代碼可能會拋棄一個或多個例外。

2.3.2 catch語句 

catch語句的參數相似於方法的聲明,包括一個例外類型和一個例外對象。例外類型必須爲Throwable類的子類,它指明瞭catch語句所處理的例外類型,例外對象則由運行時系統在try所指定的代碼塊中生成並被捕獲,大括號中包含對象的處理,其中能夠調用對象的方法。
catch語句能夠有多個,分別處理不一樣類的例外。Java運行時系統從上到下分別對每一個catch語句處理的例外類型進行檢測,直到找到類型相匹配的catch語句爲止。這裏,類型匹配指catch所處理的例外類型與生成的例外對象的類型徹底一致或者是它的父類,所以,catch語句的排列順序應該是從特殊到通常。
也能夠用一個catch語句處理多個例外類型,這時它的例外類型參數應該是這多個例外類型的父類,程序設計中要根據具體的狀況來選擇catch語句的例外處理類型。 

2.3.3 finally語句

try所限定的代碼中,當拋棄一個例外時,其後的代碼不會被執行。經過finally語句能夠指定一塊代碼。不管try所指定的程序塊中拋棄或不拋棄例外,也不管catch語句的例外類型是否與所拋棄的例外的類型一致,finally所指定的代碼都要被執行,它提供了統一的出口。一般在finally語句中能夠進行資源的清除工做。如關閉打開的文件等。

2.3.4 throws語句 

throws老是出如今一個函數頭中,用來標明該成員函數可能拋出的各類異常。對大多數Exception子類來講,Java 編譯器會強迫你聲明在一個成員函數中拋出的異常的類型。若是異常的類型是Error或 RuntimeException, 或它們的子類,這個規則不起做用, 由於這在程序的正常部分中是不期待出現的。 若是你想明確地拋出一個RuntimeException,你必須用throws語句來聲明它的類型。

2.3.5 throw語句 

throw老是出如今函數體中,用來拋出一個異常。程序會在throw語句後當即終止,它後面的語句執行不到,而後在包含它的全部try塊中(可能在上層調用函數中)從裏向外尋找含有與其匹配的catch子句的try塊。

3 關鍵字及其中語句流程詳解

3.1 try的嵌套

你能夠在一個成員函數調用的外面寫一個try語句,在這個成員函數內部,寫另外一個try語句保護其餘代碼。每當遇到一個try語句,異常的框架就放到堆棧上面,直到全部的try語句都完成。若是下一級的try語句沒有對某種異常進行處理,堆棧就會展開,直到遇到有處理這種異常的try語句。下面是一個try語句嵌套的例子。

 1 class MultiNest {
 2     static void procedure() {
 3         try {
 4             int a = 0;
 5             int b = 42/a;
 6         } catch(java.lang.ArithmeticException e) {
 7             System.out.println("in procedure, catch ArithmeticException: " + e);
 8         }
 9     }
10     public static void main(String args[]) {
11         try {
12             procedure();
13         } catch(java.lang. Exception e) {
14             System.out.println("in main, catch Exception: " + e);
15         }
16     }
17 }

這個例子執行的結果爲:

in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero

 

成員函數procedure裏有本身的try/catch控制,因此main不用去處理 ArrayIndexOutOfBoundsExc

eption;固然若是如同最開始咱們作測試的例子同樣,在procedure中catch到異常時使用throw e;語句將異常拋出,那麼main固然仍是可以捕捉並處理這個procedure拋出來的異常。例如在procedure函數的catch中的System.out語句後面增長throw e;語句以後,執行結果就變爲:

in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
in main, catch Exception: java.lang.ArithmeticException: / by zero

3.2 try-catch程序塊的執行流程以及執行結果

相對於try-catch-finally程序塊而言,try-catch的執行流程以及執行結果仍是比較簡單的。
首先執行的是try語句塊中的語句,這時可能會有如下三種狀況:
    1.若是try塊中全部語句正常執行完畢,那麼就不會有其餘的「動作」被執行,整個try-catch程序塊正常完成。
    2.若是try語句塊在執行過程當中碰到異常V,這時又分爲兩種狀況進行處理:
-->若是異常V可以被與try相應的catch塊catch到,那麼第一個catch到這個異常的catch塊(也是離try最近的一個與異常V匹配的catch塊)將被執行;若是catch塊執行正常,那麼try-catch程序塊的結果就是「正常完成」;若是該catch塊因爲緣由R忽然停止,那麼try-catch程序塊的結果就是「因爲緣由R忽然停止(completes abruptly)」。
-->若是異常V沒有catch塊與之匹配,那麼這個try-catch程序塊的結果就是「因爲拋出異常V而忽然停止(completes abruptly)」。
    3. 若是try因爲其餘緣由R忽然停止(completes abruptly),那麼這個try-catch程序塊的結果就是「因爲緣由R忽然停止(completes abruptly)」。

3.3 try-catch-finally程序塊的執行流程以及執行結果

try-catch-finally程序塊的執行流程以及執行結果比較複雜。
首先執行的是try語句塊中的語句,這時可能會有如下三種狀況:
1.若是try塊中全部語句正常執行完畢,那麼finally塊的居於就會被執行,這時分爲如下兩種狀況:
-->若是finally塊執行順利,那麼整個try-catch-finally程序塊正常完成。
-->若是finally塊因爲緣由R忽然停止,那麼try-catch-finally程序塊的結局是「因爲緣由R忽然停止(completes abruptly)」
2.若是try語句塊在執行過程當中碰到異常V,這時又分爲兩種狀況進行處理:
-->若是異常V可以被與try相應的catch塊catch到,那麼第一個catch到這個異常的catch塊(也是離try最近的一個與異常V匹配的catch塊)將被執行;這時就會有兩種執行結果:
-->若是catch塊執行正常,那麼finally塊將會被執行,這時分爲兩種狀況:
-->若是finally塊執行順利,那麼整個try-catch-finally程序塊正常完成。
-->若是finally塊因爲緣由R忽然停止,那麼try-catch-finally程序塊的結局是「因爲緣由R忽然停止(completes abruptly)」
-->若是catch塊因爲緣由R忽然停止,那麼finally模塊將被執行,分爲兩種狀況:
-->若是若是finally塊執行順利,那麼整個try-catch-finally程序塊的結局是「因爲緣由R忽然停止(completes abruptly)」。
-->若是finally塊因爲緣由S忽然停止,那麼整個try-catch-finally程序塊的結局是「因爲緣由S忽然停止(completes abruptly)」,緣由R將被拋棄。
(注意,這裏就正好和咱們的例子相符合,雖然咱們在testEx2中使用throw e拋出了異常,可是因爲testEx2中有finally塊,而finally塊的執行結果是complete abruptly的(別小看這個用得最多的return,它也是一種致使complete abruptly的緣由之一啊——後文中有關於致使complete abruptly的緣由分析),因此整個try-catch-finally程序塊的結果是「complete abruptly」,因此在testEx1中調用testEx2時是捕捉不到testEx1中拋出的那個異常的,而只能將finally中的return結果獲取到。
若是在你的代碼中指望經過捕捉被調用的下級函數的異常來給定返回值,那麼必定要注意你所調用的下級函數中的finally語句,它有可能會使你throw出來的異常並不能真正被上級調用函數可見的。固然這種狀況是能夠避免的,以testEx2爲例:若是你必定要使用finally並且又要將catch中throw的e在testEx1中被捕獲到,那麼你去掉testEx2中的finally中的return就能夠了。
這個事情已經在OMC2.0的MIB中出現過啦:服務器的異常不能徹底被反饋到客戶端。)
-->若是異常V沒有catch塊與之匹配,那麼finally模塊將被執行,分爲兩種狀況:
-->若是finally塊執行順利,那麼整個try-catch-finally程序塊的結局就是「因爲拋出異常V而忽然停止(completes abruptly)」。
-->若是finally塊因爲緣由S忽然停止,那麼整個try-catch-finally程序塊的結局是「因爲緣由S忽然停止(completes abruptly)」,異常V將被拋棄。
3.若是try因爲其餘緣由R忽然停止(completes abruptly),那麼finally塊被執行,分爲兩種狀況:
-->若是finally塊執行順利,那麼整個try-catch-finally程序塊的結局是「因爲緣由R忽然停止(completes abruptly)」。
-->若是finally塊因爲緣由S忽然停止,那麼整個try-catch-finally程序塊的結局是「因爲緣由S忽然停止(completes abruptly)」,緣由R將被拋棄。
3.4 try-catch-finally程序塊中的return
從上面的try-catch-finally程序塊的執行流程以及執行結果一節中能夠看出不管try或catch中發生了什麼狀況,finally都是會被執行的,那麼寫在try或者catch中的return語句也就不會真正的從該函數中跳出了,它的做用在這種狀況下就變成了將控制權(語句流程)轉到finally塊中;這種狀況下必定要注意返回值的處理。
例如,在try或者catch中return false了,而在finally中又return true,那麼這種狀況下不要期待你的try或者catch中的return false的返回值false被上級調用函數獲取到,上級調用函數可以獲取到的只是finally中的返回值,由於try或者catch中的return語句只是轉移控制權的做用。
3.5 如何拋出異常
若是你知道你寫的某個函數有可能拋出異常,而你又不想在這個函數中對異常進行處理,只是想把它拋出去讓調用這個函數的上級調用函數進行處理,那麼有兩種方式可供選擇:
第一種方式:直接在函數頭中throws SomeException,函數體中不須要try/catch。好比將最開始的例子中的testEx2改成下面的方式,那麼testEx1就能捕捉到testEx2拋出的異常了。

 1 boolean testEx2() throws Exception{
 2         boolean ret = true;
 3         int b=12;
 4         int c;
 5         for (int i=2;i>=-2;i--){
 6             c=b/i;
 7             System.out.println("i="+i);
 8         }
 9         return true;   
10 }

第二種方式:使用try/catch,在catch中進行必定的處理以後(若是有必要的話)拋出某種異常。例如上面的testEx2改成下面的方式,testEx1也能捕獲到它拋出的異常:

 1 boolean testEx2() throws Exception{
 2         boolean ret = true;
 3         try{
 4             int b=12;
 5             int c;
 6             for (int i=2;i>=-2;i--){
 7                 c=b/i;
 8                 System.out.println("i="+i);
 9             }
10             return true;
11         }catch (Exception e){
12             System.out.println("testEx2, catch exception");
13             Throw e;
14         }
15 }

第三種方法:使用try/catch/finally,在catch中進行必定的處理以後(若是有必要的話)拋出某種異常。例如上面的testEx2改成下面的方式,testEx1也能捕獲到它拋出的異常:

 1 boolean testEx2() throws Exception{
 2         boolean ret = true;
 3         try{
 4             int b=12;
 5             int c;
 6             for (int i=2;i>=-2;i--){
 7                 c=b/i;
 8                 System.out.println("i="+i);
 9                 throw new Exception("aaa");
10             }
11             return true;
12         }catch (java.lang.ArithmeticException e){
13             System.out.println("testEx2, catch exception");
14             ret = false;
15             throw new Exception("aaa");
16         }finally{
17             System.out.println("testEx2, finally; return value="+ret);
18         }
19 }

4.關於abrupt completion

前面提到了complete abruptly(暫且理解爲「忽然停止」或者「異常結束」吧),它主要包含了兩種大的情形:abrupt completion of expressions and statements,下面就分兩種狀況進行解釋。

4.1 Normal and Abrupt Completion of Evaluation

每個表達式(expression)都有一種使得其包含的計算得以一步步進行的正常模式,若是每一步計算都被執行且沒有異常拋出,那麼就稱這個表達式「正常結束(complete normally)」;若是這個表達式的計算拋出了異常,就稱爲「異常結束(complete abruptly)」。異常結束一般有一個相關聯的緣由(associated reason),一般也就是拋出一個異常V。
與表達式、操做符相關的運行期異常有:
-->A class instance creation expression, array creation expression , or string concatenation operatior expression throws an OutOfMemoryError if there is insufficient memory available.
-->An array creation expression throws a NegativeArraySizeException if the value of any dimension expression is less than zero.
-->A field access throws a NullPointerException if the value of the object reference  expression is null.
-->A method invocation expression that invokes an instance method throws a NullPointerException if the target reference is null.
-->An array access throws a NullPointerException if the value of the array reference  expression is null.
-->An array access throws an ArrayIndexOutOfBoundsException if the value of the array index expression is negative or greater than or equal to the length of the array.
-->A cast throws a ClassCastException if a cast is found to be impermissible at run time.
-->An integer division or integer remainder operator throws an ArithmeticException if the value of the right-hand operand expression is zero. -->An assignment to an array component of reference type throws an ArrayStoreException when the value to be assigned is not compatible with the component type of the array.

4.2 Normal and Abrupt Completion of Statements

正常狀況咱們就很少說了,在這裏主要是列出了abrupt completion的幾種狀況:
-->break, continue, and return 語句將致使控制權的轉換,從而使得statements不能正常地、完整地執行。
-->某些表達式的計算也可能從java虛擬機拋出異常,這些表達式在上一小節中已經總結過了;一個顯式的的throw語句也將致使異常的拋出。拋出異常也是致使控制權的轉換的緣由(或者說是阻止statement正常結束的緣由)。
若是上述事件發生了,那麼這些statement就有可能使得其正常狀況下應該都執行的語句不能徹底被執行到,那麼這些statement也就是被稱爲是complete abruptly.
致使abrupt completion的幾種緣由:
-->A break with no label
-->A break with a given label
-->A continue with no label
-->A continue with a given label
-->A return with no value
-->A return with a given value A
-->throw with a given value, including exceptions thrown by the Java virtual machine

下面看一個例子(例1),來說解java裏面中try、catch、finally的處理流程

 1 public class TryCatchFinally {
 2  
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6  
 7         try {
 8             t = "try";
 9             return t;
10         } catch (Exception e) {
11             // result = "catch";
12             t = "catch";
13             return t;
14         } finally {
15             t = "finally";
16         }
17     }
18  
19     public static void main(String[] args) {
20         System.out.print(TryCatchFinally.test());
21     }
22  
23 }

  首先程序執行try語句塊,把變量t賦值爲try,因爲沒有發現異常,接下來執行finally語句塊,把變量t賦值爲finally,而後return t,則t的值是finally,最後t的值就是finally,程序結果應該顯示finally,可是實際結果爲try。爲何會這樣,咱們不妨先看看這段代碼編譯出來的class對應的字節碼,看虛擬機內部是如何執行的。

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

系統運行環境:mac os lion系統 64bit

jdk信息:Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-11M3527) Java HotSpot(TM) 64-Bit Server VM (build 20.4-b02-402, mixed mode)

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

 1 public static final java.lang.String test();
 2   Code:
 3    Stack=1, Locals=4, Args_size=0
 4    0:    ldc    #16; //String 
 5    2:    astore_0
 6    3:    ldc    #18; //String try
 7    5:    astore_0
 8    6:    aload_0
 9    7:    astore_3
10    8:    ldc    #20; //String finally
11    10:    astore_0
12    11:    aload_3
13    12:    areturn
14    13:    astore_1
15    14:    ldc    #22; //String catch
16    16:    astore_0
17    17:    aload_0
18    18:    astore_3
19    19:    ldc    #20; //String finally
20    21:    astore_0
21    22:    aload_3
22    23:    areturn
23    24:    astore_2
24    25:    ldc    #20; //String finally
25    27:    astore_0
26    28:    aload_2
27    29:    athrow
28   Exception table:
29    from   to  target type
30     8    13   Class java/lang/Exception
31     8    24   any
32    19    24   any
33   LineNumberTable: 
34    line 5: 0
35    line 8: 3
36    line 9: 6
37    line 15: 8
38    line 9: 11
39    line 10: 13
40    line 12: 14
41    line 13: 17
42    line 15: 19
43    line 13: 22
44    line 14: 24
45    line 15: 25
46    line 16: 28
47 
48   LocalVariableTable: 
49    Start  Length  Slot  Name   Signature
50      27      0    t       Ljava/lang/String;
51      10      1    e       Ljava/lang/Exception;
52 
53   StackMapTable: number_of_entries = 2
54    frame_type = 255 /* full_frame */
55      offset_delta = 13
56      locals = [ class java/lang/String ]
57      stack = [ class java/lang/Exception ]
58    frame_type = 74 /* same_locals_1_stack_item */
59      stack = [ class java/lang/Throwable ]

首先看LocalVariableTable信息,這裏面定義了兩個變量 一個是t String類型,一個是e Exception 類型

接下來看Code部分

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

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

第7行,重點是第7行,把第s對應的值"try"付給第三個變量,可是這裏面第三個變量並無定義,這個比較奇怪

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

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

經過字節碼,咱們發現,在try語句的return塊中,return 返回的引用變量(t 是引用類型)並非try語句外定義的引用變量t,而是系統從新定義了一個局部引用t’,這個引用指向了引用t對應的值,也就是try ,即便在finally語句中把引用t指向了值finally,由於return的返回引用已經不是t ,因此引用t的對應的值和try語句中的返回值無關了。

 

下面在看一個例子:(例2)

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";
 9             return t;
10         } catch (Exception e) {
11             // result = "catch";
12             t = "catch";
13             return t;
14         } finally {
15             t = "finally";
16             return t;
17         }
18     }
19 
20     public static void main(String[] args) {
21         System.out.print(TryCatchFinally.test());
22     }
23 
24 }

這裏稍微修改了 第一段代碼,只是在finally語句塊裏面加入了 一個 return t 的表達式。

按照第一段代碼的解釋,先進行try{}語句,而後在return以前把當前的t的值try保存到一個變量t',而後執行finally語句塊,修改了變量t的值,在返回變量t。

這裏面有兩個return語句,可是程序到底返回的是try 仍是 finally。接下來咱們仍是看字節碼信息

 1 public static final java.lang.String test();
 2   Code:
 3    Stack=1, Locals=2, Args_size=0
 4    0:    ldc    #16; //String 
 5    2:    astore_0
 6    3:    ldc    #18; //String try
 7    5:    astore_0
 8    6:    goto    17
 9    9:    astore_1
10    10:    ldc    #20; //String catch
11    12:    astore_0
12    13:    goto    17
13    16:    pop
14    17:    ldc    #22; //String finally
15    19:    astore_0
16    20:    aload_0
17    21:    areturn
18   Exception table:
19    from   to  target type
20     9     9   Class java/lang/Exception
21    16    16   any
22   LineNumberTable: 
23    line 5: 0
24    line 8: 3
25    line 9: 6
26    line 10: 9
27    line 12: 10
28    line 13: 13
29    line 14: 16
30    line 15: 17
31    line 16: 20
32 
33   LocalVariableTable: 
34    Start  Length  Slot  Name   Signature
35      19      0    t       Ljava/lang/String;
36      6      1    e       Ljava/lang/Exception;
37 
38   StackMapTable: number_of_entries = 3
39    frame_type = 255 /* full_frame */
40      offset_delta = 9
41      locals = [ class java/lang/String ]
42      stack = [ class java/lang/Exception ]
43    frame_type = 70 /* same_locals_1_stack_item */
44      stack = [ class java/lang/Throwable ]
45    frame_type = 0 /* same */

這段代碼翻譯出來的字節碼和第一段代碼徹底不一樣,仍是繼續看code屬性

第[0-2]行、[3-5]行第一段代碼邏輯相似,就是初始化t,把try中的t進行賦值try

第6行,這裏面跳轉到第17行,[17-19]就是執行finally裏面的賦值語句,把變量t賦值爲finally,而後返回t對應的值

咱們發現try語句中的return語句給忽略。可能jvm認爲一個方法裏面有兩個return語句並無太大的意義,因此try中的return語句給忽略了,直接起做用的是finally中的return語句,因此此次返回的是finally。

 

接下來在看看複雜一點的例子:(例3)

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";
 9             Integer.parseInt(null);
10             return t;
11         } catch (Exception e) {
12             t = "catch";
13             return t;
14         } finally {
15             t = "finally";
16             // System.out.println(t);
17             // return t;
18         }
19     }
20 
21     public static void main(String[] args) {
22         System.out.print(TryCatchFinally.test());
23     }
24 
25 }

這裏面try語句裏面會拋出 java.lang.NumberFormatException,因此程序會先執行catch語句中的邏輯,t賦值爲catch,在執行return以前,會把返回值保存到一個臨時變量裏面t ',執行finally的邏輯,t賦值爲finally,可是返回值和t',因此變量t的值和返回值已經沒有關係了,返回的是catch

例4:

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";
 9             Integer.parseInt(null);
10             return t;
11         } catch (Exception e) {
12             t = "catch";
13             return t;
14         } finally {
15             t = "finally";
16             return t;
17         }
18     }
19 
20     public static void main(String[] args) {
21         System.out.print(TryCatchFinally.test());
22     }
23 
24 }

這個和例2有點相似,因爲try語句裏面拋出異常,程序轉入catch語句塊,catch語句在執行return語句以前執行finally,而finally語句有return,則直接執行finally的語句值,返回finally

例5:

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";
 9             Integer.parseInt(null);
10             return t;
11         } catch (Exception e) {
12             t = "catch";
13             Integer.parseInt(null);
14             return t;
15         } finally {
16             t = "finally";
17             //return t;
18         }
19     }
20 
21     public static void main(String[] args) {
22         System.out.print(TryCatchFinally.test());
23     }
24 
25 }

這個例子在catch語句塊添加了Integer.parser(null)語句,強制拋出了一個異常。而後finally語句塊裏面沒有return語句。繼續分析一下,因爲try語句拋出異常,程序進入catch語句塊,catch語句塊又拋出一個異常,說明catch語句要退出,則執行finally語句塊,對t進行賦值。而後catch語句塊裏面拋出異常。結果是拋出java.lang.NumberFormatException異常

例子6:

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";
 9             Integer.parseInt(null);
10             return t;
11         } catch (Exception e) {
12             t = "catch";
13             Integer.parseInt(null);
14             return t;
15         } finally {
16             t = "finally";
17             return t;
18         }
19     }
20 
21     public static void main(String[] args) {
22         System.out.print(TryCatchFinally.test());
23     }
24 
25 }

這個例子和上面例子中惟一不一樣的是,這個例子裏面finally 語句裏面有return語句塊。try catch中運行的邏輯和上面例子同樣,當catch語句塊裏面拋出異常以後,進入finally語句快,而後返回t。則程序忽略catch語句塊裏面拋出的異常信息,直接返回t對應的值 也就是finally。方法不會拋出異常

例子7:

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";
 9             Integer.parseInt(null);
10             return t;
11         } catch (NullPointerException e) {
12             t = "catch";
13             return t;
14         } finally {
15             t = "finally";
16         }
17     }
18 
19     public static void main(String[] args) {
20         System.out.print(TryCatchFinally.test());
21     }
22 
23 }

這個例子裏面catch語句裏面catch的是NPE異常,而不是java.lang.NumberFormatException異常,因此不會進入catch語句塊,直接進入finally語句塊,finally對s賦值以後,由try語句拋出java.lang.NumberFormatException異常。

例子8:

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";
 9             Integer.parseInt(null);
10             return t;
11         } catch (NullPointerException e) {
12             t = "catch";
13             return t;
14         } finally {
15             t = "finally";
16             return t;
17         }
18     }
19 
20     public static void main(String[] args) {
21         System.out.print(TryCatchFinally.test());
22     }
23 
24 }

和上面的例子中try catch的邏輯相同,try語句執行完成執行finally語句,finally賦值s 而且返回s ,最後程序結果返回finally

例子9:

 1 public class TryCatchFinally {
 2 
 3     @SuppressWarnings("finally")
 4     public static final String test() {
 5         String t = "";
 6 
 7         try {
 8             t = "try";return t;
 9         } catch (Exception e) {
10             t = "catch";
11             return t;
12         } finally {
13             t = "finally";
14             String.valueOf(null);
15             return t;
16         }
17     }
18 
19     public static void main(String[] args) {
20         System.out.print(TryCatchFinally.test());
21     }
22 
23 }

這個例子中,對finally語句中添加了String.valueOf(null), 強制拋出NPE異常。首先程序執行try語句,在返回執行,執行finally語句塊,finally語句拋出NPE異常,整個結果返回NPE異常。

對以上全部的例子進行總結

1 try、catch、finally語句中,在若是try語句有return語句,則返回的以後當前try中變量此時對應的值,此後對變量作任何的修改,都不影響try中return的返回值

2 若是finally塊中有return 語句,則返回try或catch中的返回語句忽略。

3 若是finally塊中拋出異常,則整個try、catch、finally塊中拋出異常

因此使用try、catch、finally語句塊中須要注意的是

1 儘可能在try或者catch中使用return語句。經過finally塊中達到對try或者catch返回值修改是不可行的。

2 finally塊中避免使用return語句,由於finally塊中若是使用return語句,會顯示的消化掉try、catch塊中的異常信息,屏蔽了錯誤的發生

3 finally塊中避免再次拋出異常,不然整個包含try語句塊的方法回拋出異常,而且會消化掉try、catch塊中的異常

5 關於咱們的編程的一點建議

弄清楚try-catch-finally的執行狀況後咱們才能正確使用它。 若是咱們使用的是try-catch-finally語句塊,而咱們又須要保證有異常時可以拋出異常,那麼在finally語句中就不要使用return語句了(finally語句塊的最重要的做用應該是釋放申請的資源),由於finally中的return語句會致使咱們的throw e被拋棄,在這個try-catch-finally的外面將只能看到finally中的返回值(除非在finally中拋出異常)。(咱們須要記住:不只throw語句是abrupt completion 的緣由,return、break、continue等這些看起來很正常的語句也是致使abrupt completion的緣由。)

相關文章
相關標籤/搜索