對於因爲編程錯誤而導致的異常,或者是不能期望程序捕獲的異常(解除引用一個空指針,數組越界,除零,等等),爲了使開發人員免於處理這些異常,一些異常被命名爲非檢查型異常(即那些繼承自 RuntimeException 的異常)並且不需要進行聲明。
Checked Exception和Unchecked Exception的幾點不同之處
- 方法簽名是否需要聲明exception
- 調用該方法時是否需要捕獲exception
- exception產生的時候JVM控制程序的狀態
Sun 的“The Java Tutorial”觀點
因爲 Java 語言並不要求方法捕獲或者指定運行時異常,因此編寫只拋出運行時異常的代碼或者使得他們的所有異常子類都繼承自RuntimeException
,對於程序員來說是有吸引力的。這些編程捷徑都允許程序員編寫 Java 代碼而不會受到來自編譯器的所有挑剔性錯誤的干擾,並且不用去指定或者捕獲任何異常。儘管對於程序員來說這似乎比較方便,但是它迴避了
Java 的捕獲或者指定要求的意圖,並且對於那些使用您提供的類的程序員可能會導致問題。檢查型異常代表關於一個合法指定的請求的操作的有用信息,調用者可能已經對該操作沒有控制,並且調用者需要得到有關的通知 —— 例如,文件系統已滿,或者遠端已經關閉連接,或者訪問權限不允許該動作。
如果您僅僅是因爲不想指定異常而拋出一個
RuntimeException
,或者創建RuntimeException
的一個子類,那麼您換取到了什麼呢?您只是獲得了拋出一個異常而不用您指定這樣做的能力。換句話說,這是一種用於避免文檔化方法所能拋出的異常的方式。在什麼時候這是有益的?也就是說,在什麼時候避免註明一個方法的行爲是有益的?答案是“幾乎從不。”
換句話說,Sun 告訴我們檢查型異常應該是準則。該教程通過多種方式繼續說明,通常應該拋出異常,而不是
RuntimeException
—— 除非您是 JVM。
在 Effective Java: Programming Language Guide一書中(請參閱參考資料),Josh Bloch 提供了下列關於檢查型和非檢查型異常的知識點,這些與 “The Java Tutorial” 中的建議相一致(但是並不完全嚴格一致):
- 第 39 條:只爲異常條件使用異常。也就是說,不要爲控制流使用異常,比如,在調用
Iterator.next()
時而不是在第一次檢查Iterator.hasNext()
時捕獲NoSuchElementException
。 - 第 40 條:爲可恢復的條件使用檢查型異常,爲編程錯誤使用運行時異常。這裏,Bloch 迴應傳統的 Sun 觀點 —— 運行時異常應該只是用於指示編程錯誤,例如違反前置條件。
- 第 41 條:避免不必要的使用檢查型異常。換句話說,對於調用者不可能從其中恢復的情形,或者惟一可以預見的響應將是程序退出,則不要使用檢查型異常。
- 第 43 條:拋出與抽象相適應的異常。換句話說,一個方法所拋出的異常應該在一個抽象層次上定義,該抽象層次與該方法做什麼相一致,而不一定與方法的底層實現細節相一致。例如,一個從文件、數據庫或者 JNDI 裝載資源的方法在不能找到資源時,應該拋出某種
ResourceNotFound
異常(通常使用異常鏈來保存隱含的原因),而不是更底層的IOException
、SQLException
或者NamingException
。
一、Java中異常概述
1.1Java異常結構
Throwable可以用來表示任何可以被作爲異常拋出的類。Throwable對象派生出兩種類型:Error和Exception,前者用來表示編譯時和系統錯誤,程序員往往不必關心;後者是可以被拋出的基本類型,需要程序員關注。RuntimeException是Exception的派生類,不同點將在2.2與2.3小結中描述。
Java的異常(Exception)按照編譯器檢查方式又可以分爲檢查型異常(CheckedException)和非檢查型異常(UncheckedException)。
1.2檢查型異常(CheckedException)
在Java中所有不是RuntimeException派生的Exception都是檢查型異常。當函數中存在拋出檢查型異常的操作時該函數的函數聲明中必須包含throws語句。調用改函數的函數也必須對該異常進行處理,如不進行處理則必須在調用函數上聲明throws語句。
檢查型異常是JAVA首創的,在編譯期對異常的處理有強制性的要求。在JDK代碼中大量的異常屬於檢查型異常,包括IOException,SQLException等等。
1.3非檢查型異常(UncheckedException)
在Java中所有RuntimeException的派生類都是非檢查型異常,與檢查型異常相對拋出非檢查型異常可以不在函數聲明中添加throws語句,調用函數上也不需要強制處理。
常見的NullPointException,ClassCastException是常見的非檢查型異常。非檢查型異常 可以不使用try...catch進行處理,但是如果有異常產生,則異常將由JVM進行處理。對於RuntimeException的子類最好也使用異常處理機制。雖然RuntimeException的異常可以不使用try...catch進行處理,但是如果一旦發生異常,則肯定會導致程序中斷執行,所以,爲了保證程序再出錯後依然可以執行,在開發代碼時最好使用try...catch的異常處理機制進行處理。
1.4異常的關鍵字
Java異常處理涉及到五個關鍵字,分別是:try、catch、finally、throw、throws
五個關鍵字的相關語法略。
二、異常處理方式
2.1異常鏈
在JDK1.4以後版本中,Throwable類支持異常鏈機制。Throwable 包含了其線程創建時線程執行堆棧的快照。它還包含了給出有關錯誤更多信息的消息字符串。最後,它還可以包含 cause(原因):另一個導致此 throwable 拋出的 throwable。它也稱爲異常鏈 設施,因爲 cause 自身也會有 cause,依此類推,就形成了異常鏈,每個異常都是由另一個異常引起的。
通俗的說,異常鏈就是把原始的異常包裝爲新的異常類,並在新的異常類中封裝了原始異常類,這樣做的目的在於找到異常的根本原因。
2.2異常的轉譯
異常轉譯就是將一種異常轉換另一種新的異常並且再拋出的過程,異常轉譯的目的是將系統中出現的不同類型的異常進行型別的統一,以便於異常的統一處理。
絕大多數情況下轉譯出的“結果異常”類型都是自定義異常,並且在異常轉譯過程中需要將“原始異常”放置在異常鏈中。
2.3自定義異常
自定義異常就是自寫的繼承了Exception或RuntimeException的異常類。實現自定義異常的目的大致可分爲以下三種:
1. 使用統一的類型標識多種不同型別的異常。
2. 在產生異常時更好的進行信息傳遞。常見的手段是在異常中定義異常碼,異常信息,環境對象等字段。
3. 將檢查型異常轉換爲非檢查型異常。
三異常處理
3.1關於檢查型異常與非檢查型異常的爭論
在實際編程過程中使用檢查型異常與非檢查型異常的時機從JAVA語言產生的那一天開始就已經產生。
最爲官方的說法可以參考Java最核心設計者之一JOSHUA BLOCH的《Effective Java》異常使用章節,他的主張是:對可恢復的情況使用檢查型異常,對編程錯誤使用運行時異常。
雖然上述說法有着“皇家血統”但事實上在我看來Java的檢查型異常是一個非常失敗的作品,因爲檢查型異常具有超強的“污染性”,它的出現所帶來的麻煩遠比好處要多得多。我的觀點是:幾乎在所有的情況下都不應當使用檢查型異常。當遇到檢查型異常無法處理的情況時,應該使用異常轉譯轉換爲非檢查型異常再拋出。我非常興奮的看到在Think in Java 4th Edition上作者對這樣的觀點進行了詳細的描述。
Java創造檢查型異常的初衷是在編譯期強制程序員對異常情況進行處理,從而使得程序更加的強壯可靠,可是Java的作者忘記了:好的程序設計語言能幫助程序員寫出好程序,但無論那種語言都避免不了程序員用它寫出壞程序。
對於異常處理的關鍵點並不在於是在編譯期還是運行期對異常進行檢查,而在於異常一定要檢查並且需要建立統一的、一致的異常檢查與處理模型。
3.2我的異常處理原則
1. 僅處理當前可處理的異常。
2. 對所有的檢查型異常使用異常轉譯。
3. 所有的自定義異常都是非檢查型異常。
4. 異常流程與正常流程進行分離,並儘可能的統一處理。
5. 在非異常處理模塊的catch塊中儘可能不記日誌。
6. 除非是進行資源釋放操作,否則catch塊不應爲空或者出現e.printTrace
7. finally塊中不能出現複雜的操作,且不可以拋出異常,也不可以出現return。
3.3我處理異常的一般方式
1. 將throw語句視爲異常流程的起點,將Exception對象視作正常流程向異常流程躍遷過程中的數據載體。
2. 建立統一的自定義異常類型,用以包裝所有檢查型異常。
3. 大多數情況下僅在程序的主幹上建立唯一的異常捕獲點,並在這個點上對接收到的異常進行處理。