From Gossip@Openhome

Java Gossip: 例外的繼承架構

Java的例外處理機制並不是只有將程式邏輯與例外處理分開的好處,程式設計的錯誤情況很多且難以估計,並沒有人能保證自已所設計的程式完全無誤,例外處理最重要的是為程式設計人員提供種種可能的例外情況,讓程式設計人員能夠掌握並設法排除。

Java編譯器會檢查程式語法等的相關錯誤,這些錯誤是屬於「編譯時期錯誤」,然而語法無誤並不代表程式邏輯沒有錯誤,邏輯上的錯誤會在程式執行時發生,這是屬於「執行時期錯誤」,而即使邏輯沒有錯誤,也可能因為I/O、網路或甚至記憶體不足等情況而發生錯誤。

Java所處理的例外主要可分為兩大類,一種是嚴重的錯誤,例如硬體錯誤或記憶體不足等問題,與此相關的類別是位於java.lang下的Error類別;另一種是非嚴重的錯誤,代表可以處理的狀況,與此相關的是位於java.lang下的Exception類別

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


getLocalizedMessage() 目前物件的描述
getMessage() 取得物件的錯誤訊息
printStackTrace() 取得堆疊中的訊息


除了使用這些方法之外,我們也可以利用toString()取得例外物件的錯誤描述。

您所處理的例外通常都是衍生自Exception類別,其中大部份是執行時期例外(RuntimeException), 例如 ArithmeticException、ArrayIndexOutOfBoundsException等等,另外還有一些非執行時期例外,例如 ClassNotFoundException(嘗試載入類別時失敗所引發,例如類別檔案不存在)、InterruptedException(執行緒非 執行中而嘗試中斷所引發的例外)等等, 以下列出一些重要的繼承架構:
Throwable
  Error(嚴重的系統錯誤)
    LinkageError
    ThreadDeath
    VirtualMachineError
    ....
  Exception
    ClassNotFoundException
    CloneNotSupportedException
    IllegalAccessException
    ....
    RuntimeException(執行時期例外)
      ArithmeticException
      ArrayStoreException
      ClassCastException
      ....

屬於RuntimeException衍生出來的類別,是在執行時期會發生的,不需要特別使用try-catch或是在函式上使用"throws"宣告也 可以通過編譯,例如您在使用陣列時,並不一定要處理ArrayIndexOutOfBoundsException例外。

Exception下非RuntimeException衍生之例外如果有引發的可能性,則您一定要在程式中明確的指定處理才可以通過編譯,例如當您使用 到BufferedReader類別時,由於有可能引發IOException,您要不就在try-catch中處理,要不就在函式上使用throws表 示由呼叫它的函式來處理。

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

  • UseException.java
import java.io.*; 

public class UseException {
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());
}
}
}

這個程式若在編譯時將會產生以下的錯誤訊息:
UseException.java:11: exception java.lang.ArithmeticException has already been caught
catch(ArithmeticException e) {
^
1 error

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

  • UseException.java
import java.io.*; 

public class UseException {
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());
}
}
}

執行結果:
java.lang.ArithmeticException: 例外測試 

在撰寫程式時,您也可以如上將Exception例外物件的捕捉撰寫在最後,以便捕捉到所有尚未考慮到的例外,並進一步改進程式。

如果您要自訂自已的例外類別,您可以繼承Exception類別而不是Error,Error是屬於嚴重的系統錯誤,您不用去處理它,您也可以繼承 RuntimeException類別,就如之前所說過的,這個例外不一用明確使用try-catch來處理也可以通過編譯,但通常建議的是繼承 Exception,至少這樣的程式會是比較安全的,對於可處理的這些例外,您在程式中必須明確的解決它,如果沒有,編譯器會告訴您。

一些程式會自行繼承相關的例外類別,包括一些相關的例外訊息,它們也會在定義介面(interface)時於方法上聲明throws某些類型的例外,然而 如果您在這些方法中發生了某些不是方法聲明的例外(可能由於使用的底層技術不同而有這種情況),您就無法將之throw,只能自行撰寫一些try.. catch來暗自處理掉,如果想要讓這些例外丟出至上層,就要更多道的手續了,例如撰寫一個類繼承RuntimeException,在發生例外時將該例 外包裝至這個RuntimeException類中,然後再丟出。