例外處理的本意是,在程式錯誤發生時,能夠有明確的方式通知API客戶端,讓客戶端採取進一步的動作修正錯誤,而就撰寫本文的時間點來說,Java是唯一採用受檢例外(Checked exception)的語言,這有兩個目的:一是文件化,受檢例外宣告會是API操作介面的一部份,客戶端只要查閱文件,就可以知道方法可能會引發哪些例外,並事先加以處理,而這是API設計者決定是否拋出受檢例外時的考量之一,另一個目的是提供編譯器資訊,讓編譯器能夠在編譯時期就檢查出API客戶端沒有處理例外。
問題是有些錯誤發生而引發例外時,你根本無力回復,例如SQLException
是受檢例外,如果例外的發生原因是資料庫連線異常,而連線異常的原因是由於實體線路問題,那麼無論如何你都不可能使用try
、catch
回復到正常可運作的情況。
public Customer getCustomer(String id) throws SQLException {
...
}
看起來似乎沒有問題,但假設這個方法是在整個應用程式非常底層被呼叫,在某個
SQLException
發生時,最好的方法是將例外浮現至呈現層,例如網頁技術,將錯誤訊息於網頁上顯示出來給管理人員。為了讓例外往上浮現,你也許會選擇在每個方法呼叫上都宣告
throws SQLException
,但前面假設,這個方法的呼叫是在整個應用程式的底層,這樣的作法也許會造成許多程式碼的修改(更別說要重新編譯了),另一個問題是,如果你根本無權修改應用程式的其它部份,這樣的作法顯示行不通。受檢例外本意良好,有助於程式設計人員注意到例外的可能性並加以處理,但在應用程式規模增大時,會對逐漸對維護造成困難,上述情況不一定是你自訂API時發生,也可能是在底層引入了一個會拋出受檢例外的API而發生類似情況。
重新拋出例外時,除了將捕捉到的例外直接拋出,也可以考慮為應用程式自訂專屬例外類別,讓例外更能表現應用程式特有的錯誤資訊。自訂例外類別時,可以繼承Throwable
、Error
或Exception
的相關子類別,通常建議繼承自Exception
,如果不是繼承自Error
或RuntimeException
,那麼就會是受檢例外。
public class CustomizedException extends Exception { // 自訂受檢例外的一個例子
...
}
錯誤發生時,如果上下文環境並沒有足夠的資訊讓你處理例外,你可以就現有資訊處理完例外後,重新拋出例外,既然你已經針對錯誤做了某些處理,那麼也就可以考慮自訂例外,用以更精確地表示出未處理的錯誤,如果認為呼叫API的客戶端應當有能力處理未處理的錯誤,那就自訂受檢例外、填入適當錯誤訊息並重新拋出,並在方法上使用throws
加以宣告,如果認為呼叫API的客戶端沒有準備好就呼叫了方法,才會造成還有未處理的錯誤,那就自訂非受檢例外、填入適當錯誤訊息並重新拋出。
public class CustomizedException extends RuntimeException { // 自訂非受檢例外的一個例子
...
}
一個基本的例子是這樣的:
try {
....
} catch(SomeException ex) {
// 作些可行的處理
// 也許是 Logging 之類的
throw new CustomizedException("error message..."); // Checked 或 Unchecked?
}
類似地,如果流程中要拋出例外,也要思考一下,這是客戶端可以處理的例外嗎?還是客戶端沒有準備好前置條件就呼叫方法,才引發的例外?
if(someCondition) {
throw new CustomizedException("error message"); // Checked 或 Unchecked?
}
無論如何,Java採用了受檢例外的作法,Java的標準API似乎也打算一直這麼區分下去,只是受檢例外讓開發人員無從選擇,會由編譯器強制性要求處理,確實會在設計上造成麻煩,因而有些開發者在設計程式庫時,乾脆就選擇完全使用非受檢例外,一 些會封裝應用程式底層行為的框架,如Spring或Hibernate,就選擇了讓例外體系是非受檢例外,例如Spring中的DataAccessException
,或者是Hibernate 3中的HibernateException
,它們選擇給予開發人員較大的彈性來面對例外(也許也需要開發人員更多的經驗)。
實際上確實有這類例子,Hibernate 2中的
HibernateException
是受檢例外,然而Hibernate 3中的HibernateException
變成了非受檢例外。然而,即使不用面對受檢例外與非受檢例外的區別,開發者仍然必須思考,這是客戶端可以處理的例外嗎?還是客戶端沒有準備好前置條件就呼叫方法,才引發的例外?