Java中try和catch的故事

1.一個try和它的catch語句形成了一個單元。catch子句的範圍限制於try語句前面所定義的語句。一個catch語句不能捕獲另一個try聲明所引發的異常(除非是嵌套的try語句情況)。  

被try保護的語句聲明必須在一個大括號之內(也就是說,它們必須在一個塊中)。你不能單獨使用try。

2. 例外是在程序運行過程中發生的異常事件,比如除0溢出、數組越界、文件找不到等,這些事件的發生將阻止程序的正常運行。爲了加強程序的魯棒性,程序設計時,必須考慮到可能發生的異常事件並做出相應的處理。C語言中,通過使用if語句來判斷是否出現了例外,同時,調用函數通過被調用函數的返回值感知在被調用函數中產生的例外事件並進行處理。全程變量ErroNo常常用來反映一個異常事件的類型。但是,這種錯誤處理機制會導致不少問題。
Java通過面向對象的方法來處理例外。在一個方法的運行過程中,如果發生了例外,則這個方法生成代表該例外的一個對象,並把它交給運行時系統,運行時系統尋找相應的代碼來處理這一例外。我們把生成例外對象並把它提交給運行時系統的過程稱爲拋棄(throw)一個例外。運行時系統在方法的調用棧中查找,從生成例外的方法開始進行回朔,直到找到包含相應例外處理的方法爲止,這一個過程稱爲捕獲(catch)一個例外。
2.1 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語句嵌套的例子。
class MultiNest {
    static void procedure() {
        try {
            int a = 0;
            int b = 42/a;
        } catch(java.lang.ArithmeticException e) {
            System.out.println("in procedure, catch ArithmeticException: " + e);
        }
    }
    public static void main(String args[]) {
        try {
            procedure();
        } catch(java.lang. Exception e) {
            System.out.println("in main, catch Exception: " + e);
        }
    }
}
這個例子執行的結果爲:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
成員函數procedure裏有自己的try/catch控制,所以main不用去處理 ArrayIndexOutOfBoundsException;當然如果如同最開始我們做測試的例子一樣,在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拋出的異常了。
    boolean testEx2() throws Exception{
        boolean ret = true;
        int b=12;
        int c;
        for (int i=2;i>=-2;i--){
            c=b/i;
            System.out.println("i="+i);
        }
        return true;   
}
第二種方式:使用try/catch,在catch中進行一定的處理之後(如果有必要的話)拋出某種異常。例如上面的testEx2改爲下面的方式,testEx1也能捕獲到它拋出的異常:
    boolean testEx2() throws Exception{
        boolean ret = true;
        try{
            int b=12;
            int c;
            for (int i=2;i>=-2;i--){
                c=b/i;
                System.out.println("i="+i);
            }
            return true;
        }catch (Exception e){
            System.out.println("testEx2, catch exception");
            Throw e;
        }
    }
第三種方法:使用try/catch/finally,在catch中進行一定的處理之後(如果有必要的話)拋出某種異常。例如上面的testEx2改爲下面的方式,testEx1也能捕獲到它拋出的異常:
    boolean testEx2() throws Exception{
        boolean ret = true;
        try{
            int b=12;
            int c;
            for (int i=2;i>=-2;i--){
                c=b/i;
                System.out.println("i="+i);
                throw new Exception("aaa");
            }
            return true;
        }catch (java.lang.ArithmeticException e){
            System.out.println("testEx2, catch exception");
            ret = false;
            throw new Exception("aaa");
        }finally{
            System.out.println("testEx2, finally; return value="+ret);
        }
    }

4. 儘管由Java運行時系統提供的默認異常處理程序對於調試是很有用的,但通常你希望自己處理異常。這樣做有兩個好處。第一,它允許你修正錯誤。第二,它防止程序自動終止。大多數用戶對於在程序終止運行和在無論何時錯誤發生都會打印堆棧軌跡感到很煩惱(至少可以這麼說)。幸運的是,這很容易避免。  

爲防止和處理一個運行時錯誤,只需要把你所要監控的代碼放進一個try塊就可以了。緊跟着try塊的,包括一個說明你希望捕獲的錯誤類型的catch子句。完成這個任務很簡單,下面的程序包含一個處理因爲被零除而產生的ArithmeticException 異常的try塊和一個catch子句。 

class Exc2 { 

    public static void main(String args[]) {         int d, a; 

        try { // monitor a block of code.             d = 0;             a = 42 / d; 

            System.out.println("This will not be printed."); 

        } catch (ArithmeticException e) { // catch divide-by-zero error             System.out.println("Division by zero.");         } 

        System.out.println("After catch statement.");     } }  

該程序輸出如下: Division by zero. 

After catch statement.  

注意在try塊中的對println( )的調用是永遠不會執行的。一旦異常被引發,程序控制由try塊轉到catch塊。執行永遠不會從catch塊"返回"到try塊。因此,"This will not be printed。

5.小編我看了一晚上也沒搞清楚try和catch在訪問成員變量時的用處,只是搞到了代碼溫習了一下,查閱了資料之後,覺得大部分try和catch都是處理異常也就是大家常說的例外.可能大佬們說魯棒性說習慣了,萌新們也追風了.

6.最後給大家一個訪問成員變量的關鍵代碼,發給大家參考

import java.lang.reflect.*;

public class Main_02 {

    public static void main(String[] args) {

        Example_02 example = new Example_02();

        Class exampleC = example.getClass();

        // 獲得所有成員變量

        Field[] declaredFields = exampleC.getDeclaredFields();

        for (int i = 0; i < declaredFields.length; i++) {

            Field field = declaredFields[i]; // 遍歷成員變量

            // 獲得成員變量名稱

            System.out.println("名稱爲:" + field.getName());

            Class fieldType = field.getType(); // 獲得成員變量類型

            System.out.println("類型爲:" + fieldType);

            boolean isTurn = true;

            while (isTurn) {

                // 如果該成員變量的訪問權限爲private,則拋出異常,即不允許訪問

                try {

                    isTurn = false;

                    // 獲得成員變量值

                    System.out.println("修改前的值爲:" + field.get(example));

                    // 判斷成員變量的類型是否爲int

                    if (fieldType.equals(int.class)) {

                        System.out.println("利用方法setInt()修改成員變量的值");

                        field.setInt(example, 168); // 爲int型成員變量賦值

                        // 判斷成員變量的類型是否爲float型

                    } else if (fieldType.equals(float.class)) {

                        System.out.println("利用方法setFloat()修改成員變量的值");

                        // 爲float型成員變量賦值

                        field.setFloat(example, 99.9F);

                        // 判斷成員變量的類型是否爲boolean型

                    } else if (fieldType.equals(boolean.class)) {

                        System.out.println("利用方法setBoolean()修改成員變量的值");

                        // 爲boolean型成員變量賦值

                        field.setBoolean(example, true);

                    } else {

                        System.out.println("利用方法set()修改成員變量的值");

                        // 可以爲各種類型的成員變量賦值

                        field.set(example, "MWQ");

                    }

                    // 獲得成員變量值

                    System.out.println("修改後的值爲:" + field.get(example));

                } catch (Exception e) {

                    System.out.println("在設置成員變量值時拋出異常,"

                            + "下面執行setAccessible()方法!");

                    field.setAccessible(true); // 設置爲允許訪問

                    isTurn = true;

                }

            }

            System.out.println();

        }

    }

}

結果爲:

名稱爲:i

類型爲:int

修改前的值爲:0

利用方法setInt()修改成員變量的值

修改後的值爲:168

 

名稱爲:f

類型爲:float

修改前的值爲:0.0

利用方法setFloat()修改成員變量的值

修改後的值爲:99.9

 

名稱爲:b

類型爲:boolean

修改前的值爲:false

利用方法setBoolean()修改成員變量的值

修改後的值爲:true

 

名稱爲:s

類型爲:class java.lang.String

在設置成員變量值時拋出異常,下面執行setAccessible()方法!

修改前的值爲:null

利用方法set()修改成員變量的值

修改後的值爲:MWQ

7.再次感謝大家的收看.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章