Java SE 6 技術手冊

10.4 例外的繼承架構

Java 所處理的例外主要可分為兩大類:一種是嚴重的錯誤,例如硬體錯誤或記憶體不足等問題,與此相關的類別是位於 java.lang 下的 Error 類別及其子類別,對於這類的錯誤通常程式是無力自行回復;另一種是非嚴重的錯誤,代表可以處理的狀況,例如使用者輸入了不合格式的資料,這種錯誤程式有機會回復至正常運作狀況,與這類錯誤相關的類別是位於 java.lang 下的 Exception 類別及其子類別。

Error 類別與 Exception 類別都繼承自 Throwable 類別,Throwable 類別擁有幾個取得相關例外訊息的方法。

  • getLocalizedMessage()

    取得例外物件的區域化訊息描述

  • getMessage()

    取得例外物件的訊息描述

  • printStackTrace()

    顯示例外的堆疊訊息,這個方法在追蹤例外發生的根源時相當的有用,簡單的說若 A 方法中呼叫了 B 方法,而 B 方法中呼叫了 C 方法,C 方法產生了例外,則在處理這個例外時呼叫 printStackTrace() 可以得知整個方法呼叫的過程,由此得知例外是如何被層層丟出的。

除了使用這些方法之外,您也可以簡單的利用例外物件 toString() 方法取得例外的簡單訊息描述。

您所接觸的例外通常都是衍生自 Exception 類別,其中是有些「受檢例外」(Checked exception),例如 ClassNotFoundException(嘗試載入類別時失敗所引發,例如類別檔案不存在)、InterruptedException(執行緒非執行中而嘗試中斷所引發的例外)等,而有些是「執行時期例外」(Runtime exception),也稱「非受檢例外」(Unckecked exception),例如 ArithmeticException、ArrayIndexOutOfBoundsException 等。以下列出一些重要的例外繼承架構:

Throwable
  Error(嚴重的系統錯誤)
    LinkageError
    ThreadDeath
    VirtualMachineError
    ....
  Exception
    ClassNotFoundException
    CloneNotSupportedException
    IllegalAccessException
    ....
    RuntimeException(執行時期例外)
      ArithmeticException
      ArrayStoreException
      ClassCastException
      ....

Exception 下非 RuntimeException 衍生之例外類別如果有引發的可能,則您一定要在程式中明確的指定處理才可以通過編譯,因為這些例外是可預期的,編譯器會要求您明確處理,所以才稱之為「受檢例外」(Checked exception),例如當您使用到 BufferedReader 的 readLine() 時,由於有可能引發 IOException 這個受檢例外,您要不就在 try...catch 中處理,要不就在方法上使用 "throws" 表示由呼叫它的呼叫者來處理。

屬於 RuntimeException 衍生出來的類別是「執行時期例外」(Runtime exception),是在執行時期會發生的例外,這個例外不預期它一定會發生,端看程式邏輯寫的如何,因而不需要特別使用 try...catch 或是在方法上使用 "throws" 宣告也可以通過編譯,所以才稱之為「非受檢例外」(Unchecked exception),例如您在使用陣列時,並不一定要處理 ArrayIndexOutOfBoundsException 例外,因為只要程式邏輯寫的正確,這個例外就不會發生。

瞭解例外處理的繼承架構是必要的,例如在捕捉例外物件時,如果父類別例外物件在子類別例外物件之前被捕捉,則 "catch" 子類別例外物件的區塊將永遠不會被執行,事實上編譯器也會幫您檢查這個錯誤,例如:

範例 10.7 ExceptionDemo.java

import java.io.*; 

public class ExceptionDemo { 
    public static void main(String[] args) { 
        try { 
            throw new ArithmeticException("例外測試"); 
        } 
        catch(Exception e) { 
            System.out.println(e.toString()); 
        } 
        catch(ArithmeticException e) { 
            System.out.println(e.toString()); 
        } 
    } 
}

因為 Exception 是 ArithmeticException 的父類別,所以 ArithmeticException 的實例會先被 Exception 的 "catch" 區塊捕捉到,範例 10.7 在編譯時將會產生以下的錯誤訊息:

ExceptionDemo.java:11: exception java.lang.ArithmeticException has already been caught
        catch(ArithmeticException e) {
        ^
1 error

要完成這個程式的編譯,您必須更改例外物件捕捉的順序,如範例 10.8 所示。

範例 10.8 ExceptionDemo2.java

import java.io.*; 

public class ExceptionDemo2 { 
    public static void main(String[] args) { 
        try { 
            throw new ArithmeticException("例外測試"); 
        } 
        catch(ArithmeticException e) { 
            System.out.println(e.toString()); 
        } 
        catch(Exception e) { 
            System.out.println(e.toString()); 
        } 
    } 
}

在撰寫程式時,您也可以如範例 10.8 將 Exception 例外物件的捕捉撰寫在最後,以便捕捉到所有您所尚未考慮到的例外,在除蟲(Debug)階段時是很有用的。

如果您要自訂自已的例外類別,您可以繼承 Exception 類別而不是 Error 類別,Error 是屬於嚴重的系統錯誤,程式通常無力從這類的錯誤中回復,所以您不用去處理它,事實上在 Java 程式中也不希望您處理 Error 類別的例外。

如果使用繼承時,父類別的某個方法上宣告了 throws 某些例外,而在子類別中重新定義該方法時,您可以:

  • 不處理例外(重新定義時不設定 throws)
  • 可僅 throws 父類別中被重新定義的方法上之某些例外
  • 可 throws 被重新定義的方法上之例外之子類別

但是您不可以:

  • throws 父類別方法中未定義的其它例外
  • throws 被重新定義的方法上之例外之父類別