1. 程式人生 > >Java程式設計思想第四版第十二章學習——通過異常處理錯誤(1)

Java程式設計思想第四版第十二章學習——通過異常處理錯誤(1)

使用異常帶來的好處:
它降低了錯誤處理程式碼的複雜度。使用異常後,不需要檢查特定的錯誤並在程式中的許多地方去處理它。因為異常機制將保證能夠捕獲這個錯誤且只需在一個地方處理錯誤,即異常處理程式中。

1、基本異常

異常情形:阻止當前方法或作用域繼續執行的問題。此時,在當前環境下無法獲得必要的資訊來解決問題,能做的就是從當前環境跳出,並把問題提交給上一級環境。
丟擲異常的例子

if(t == nullthrow new NullPointerException();

異常引數:
用new在堆上建立異常物件,也伴隨著儲存空間的分配和構造器的呼叫。所有標準異常類都有兩個構造器:一個是預設構造器,一個是接受字串作為引數,以便能把相關資訊放入異常物件的構造器:

//接受字串作為引數的構造器
throw new NullPointerException("t = null");

在使用new建立了異常物件後,此物件的引用將傳給throw。儘管返回的異常物件其型別通常與方法設計的返回型別不同,但從效果上看,就是從方法“返回”的。
注意:異常返回的“地方”與普通方法呼叫返回的“地方”完全不同。(異常將在一個恰當的異常處理程式中得到解決,它的位置可能離異常被丟擲的地方很遠,也可能會跨越方法呼叫棧的很多層次)

2、捕獲異常

如果在方法內部(方法內部呼叫的其他方法)丟擲了異常,這個方法將在丟擲異常的過程中結束。要是希望方法仍能繼續,在方法內設定try塊(一個特殊的塊,嘗試各種可能產生異常的方法呼叫)。
丟擲的程式必須在異常處理程式中得到處理。而且針對每個捕獲的異常,需要有相應的處理程式。異常處理程式緊跟在try塊之後,以關鍵字catch表示

try{
    //Code that might generate exceptions
} catch(Type1 id1){
    //Handle exceptions of Type1
}catch (Type2 id2){
    //Handle exceptions of Type2
}
………………

注意:在try塊的內部,許多不同的方法呼叫可能會產生型別相同的異常,只需提供一個針對此型別的異常處理程式就好。

異常處理基本模型:
理論上存在兩種:
(1)終止模型:錯誤非常關鍵,以至於程式無法返回到異常發生的地方繼續執行。一旦異常被丟擲,表明錯誤已無法挽回,也不能回來繼續執行。
(2)恢復模型:修正錯誤,然後重新嘗試調用出問題的方法,並認為第二次能成功。對於Java,要實現恢復模型,要求在遇見錯誤時不能丟擲異常,而是呼叫方法來修正該錯誤。或將try塊放在while迴圈裡,這樣就不斷進入try塊,直到得到滿意的結果。
目前基本使用終止模型。不採用恢復模型的主要原因:
恢復性的處理程式需要了解異常丟擲的地方,這要包含依賴於丟擲位置的非通用性程式碼。增加了程式碼編寫和維護的困難。

建立自定義異常
Java提供的異常體系並不能完全地滿足我們實際的需要,因此可以自己定義異常類。這必須從已有的異常類繼承,最好選擇意思相近的異常類繼承(並不容易找)。

class  SimpleException extends Exception {
    public SimpleException (){}
    //使用super關鍵字明確呼叫基類構造器,接受一個字串作為引數
    public SimpleException (String msg) {super(msg);}
}

public class InheritingExceptions {
    public void f() throws SimpleException{
        System.out.println("ok1 = "+Thread.currentThread().getId());
        System.out.println("Throw SimpleException from f()");
        throw new SimpleException();
    }

    public void g() throws SimpleException{
        System.out.println("ok2 = "+Thread.currentThread().getId());
        System.out.println("Throw SimpleException from g()");
        throw new SimpleException("this is the message of g()");
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        InheritingExceptions sed = new InheritingExceptions();
        System.out.println("ok_main = "+Thread.currentThread().getId());
        try{
            sed.f();
        }catch(SimpleException e){
        //printStackTrace()方法將列印“從方法呼叫處直到異常丟擲處”的
        //方法呼叫序列
            e.printStackTrace(System.out);
            System.out.println("ok_try1");
            System.out.println("fk = "+Thread.currentThread().getId());
        }

        try{
            sed.g();
        }catch(SimpleException e){
            e.printStackTrace(System.out);
            System.out.println("ok_try2");
            System.out.println("gk = "+Thread.currentThread().getId());
        }

        System.out.println("END");

    }

}/*Output
ok_main = 1
ok1 = 1
Throw SimpleException from f()
SimpleException
    at InheritingExceptions.f(InheritingExceptions.java:10)
    at InheritingExceptions.main(InheritingExceptions.java:24)
ok_try1
fk = 1
ok2 = 1
Throw SimpleException from g()
SimpleException: this is the message of g()
    at InheritingExceptions.g(InheritingExceptions.java:16)
    at InheritingExceptions.main(InheritingExceptions.java:32)
ok_try2
gk = 1
END
*///

在上面的例子中,資訊被髮送到了System.out,並自動被捕獲和顯示在輸出中。如果呼叫的是預設版本:
e.printStackTrace(); 那麼資訊將被輸出到標準錯誤流。

3、異常說明

程式庫一般不和原始碼庫一起釋出,那麼客戶端程式設計師如何方法可能丟擲的異常呢?此時就有了異常說明。它屬於方法宣告的一部分,緊跟在形式引數列表之後。形如

void f() throws TooBig, TooSmall, DivZero{//....}

可是如果這樣寫

void f() {//...}

表示不會丟擲任何異常(除了從RuntimeException繼承的異常,它們可以在沒有異常說明的情況下被丟擲)

注意的是:可以宣告方法將丟擲異常,實際上不丟擲。這麼做好處是,為異常先佔位子,以後就可以丟擲這種異常而不用修改已有的程式碼。經常用於定義抽象基類和介面。

4、捕獲所有異常

通過捕獲異常型別的基類Exception可以做到只寫一個異常處理程式來捕獲所有型別的異常。且最好把它放在處理程式列表的末尾,以防它搶在其他處理程式之前先把異常捕獲了。
Exception可以呼叫它從其基類Throwable繼承的方法:
(1)String getMessage() 和 String getLocalizedMessage():用來獲取詳細資訊。
(2)String toString() 返回對Throwable的簡單描述,如果有詳細資訊的話,也會包括在內。
(3)void printStackTrace()
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
列印Throwable和Throwable的呼叫棧軌跡。
(4)Throwable fillInStackTrace()用於在Throwable物件的內部記錄棧幀的當前狀態。在程式重新丟擲錯誤或異常時很有用。
(5)printStackTrace()方法所提供的資訊可以通過getStackTrace()方法來直接訪問。這個方法返回一個由軌跡棧中的元素所構成的陣列。元素0是棧頂元素,並且是呼叫序列中的最後一個方法呼叫(這個Throwable被建立和丟擲之處)。陣列中的最後一個元素和棧底是呼叫序列中的第一個方法呼叫。

getMessage(),getLocalizedMessage(),toString(),printStackTrace(),每個方法都比前一個提供了更多的資訊,每一個都是前一個的超集。

重新丟擲異常
可以將剛捕獲的異常重新丟擲。

catch(Exception e) {
    System.out.println("An exception was thrown");
    throw e;
}

重拋異常會把異常拋給上一級環境中的異常處理程式,同一個try塊的後續catch子句將被忽略,且異常物件的所有資訊都保留完整。
如果只是把當前異常物件重新丟擲,printStackTrace()方法顯示的是原來異常丟擲點的呼叫棧資訊。要想更新這個資訊,可以呼叫fillInStackTrace()方法,這將返回一個Throwable物件,它通過把當前呼叫棧資訊填入原來那個異常物件而建立。

異常鏈
異常鏈:在捕獲一個異常後丟擲另一個異常,並且把原始異常的資訊儲存下來。
異常鏈實現機制:Throwable的子類在構造器中可以接受一個cause物件作為引數,這個cause就用來表示原始異常,因而通過把原始異常傳遞給新的異常,使得即使在當前位置建立並丟擲了新的異常,也能通過異常鏈追蹤到異常最初發生的位置。
在Throwable的子類中,只有三種基本異常類提供了帶cause引數的構造器。它們是Error(用於Java虛擬機器報告系統錯誤)、Exception以及RuntimeException。如果要把其他型別的異常連結起來,要使用initCause()方法。