在Java中,當你需要統一處理異常的時候,你是會選擇catch (Exception),還是直接catch (Throwable)?
Java的異常體系
- Throwable: Java中所有異常和錯誤類的父類。只有這個類的實例(或者子類的實例)可以被虛擬機拋出或者被java的throw關鍵字拋出。同樣,只有其或其子類可以出現在catch子句里面。
- Error: Throwable的子類,表示嚴重的問題發生了,而且這種錯誤是不可恢復的。
- Exception: Throwable的子類,應用程序應該要捕獲其或其子類(RuntimeException例外),稱為checked exception。比如:IOException, NoSuchMethodException...
- RuntimeException: Exception的子類,運行時異常,程序可以不捕獲,稱為unchecked exception。比如:NullPointException.
應該catch什么
其實只要是Throwable和其子類都是可以throw和catch的,那么如果在需要統一處理異常的地方,我們應該catch (Throwable th) 還是 catch (Exception)呢?
這兩種處理的區別在於,catch throwable會把Error和其他繼承Throwable的類捕捉到。而catch Exception只會捕捉Exception極其子類,捕捉的范圍更小。先不考慮有其他的類繼承了Throwable的情況下(附錄A),第一種catch相當於比第二種catch多捕捉了把Error和其子類。
那么究竟Error是否需要捕捉呢?JDK中Error類的的注釋(如下)里提到過,Error是一種嚴重的問題,應用程序不應該捕捉它。
An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it.
A method is not required to declare in its throws clause any subclasses of Error that might be thrown during the execution of the method but not caught, since these errors are abnormal conditions that should never occur.
Java Lanuage Spec 7 中也提到:Error繼承自Throwable而不是繼承自Exception,是為了方便程序可以使用 "catch (Exception)"來捕捉異常而不會把Error也捕捉在內,因為Exception發生后可以進行一些恢復工作的,但是Error發生后一般是不可恢復的。
The class Error is a separate subclass ofThrowable, distinct from Exception in the class hierarchy, to allow programs to use the idiom "} catch (Exception e) { " (§11.2.3) to catch all exceptions from which recovery may be possible without catching errors from which recovery is typically not possible.
已經不難看出,Java本身設計思路就是希望大家catch Exception就足夠了,如果有Error發生,catch了也不會有什么作用(附錄B)。
引申,如何設計異常體系?
如何設計異常體系要根據你的項目的情況,類庫框架,應用程序的異常設計方式都會有一些區別。下面簡單談談個人對異常設計的一些看法
類庫/框架
- 繼承RuntimeException擴展一個新的異常作為整個類庫的異常基類。這個異常應該可以滿足大部分類庫對異常的要求。
- 在實現中,在任何需要捕捉checked exception的地方都會把異常統一轉化成這個新的異常。
- 對於有特殊需求,需要自定義異常的,就通過繼承這個基類來實現自定義異常。
- 不對異常記錄log(交給上層來處理)
- 案例
-
- spring 自定義異常比較多,不過都是繼承自org.springframework.core.NestedRuntimeException,而這個異常也是繼承自RuntimeException。
應用程序
- 設計上和框架異常類似,只是在捕捉checked exception的時候需要log
- 如果需要根據異常進行不同的處理,建議給自定義異常增加一個ERROR_CODE字段,這樣無論在服務器還是客戶端都可以根據不同的ERROR_CODE進行對應的處理。但是出現這種情況的時候,應該需要考慮一下設計思路了,一般來講根據異常來決定業務流程不是一個好的設計方案。
附錄A:是否應該直接繼承Throwable來擴展新的異常?
個人認為異常都應該繼承自Exception或者RuntimeException,而且Java本身對Exception和Error的規划就很清晰了,Java自己類庫中沒有異常是直接繼承自Throwable的。
附錄B:Error可以catch嗎? 可以catch了后做些其他處理嗎?
Error是可以catch的,而且也可以向常規Exception一樣被處理,而且就算不捕捉的話也只是導致當前線程掛掉,其他線程還是可以正常運行,如果有需要的話捕捉Error之后也可以做些其他處理。但是Error是一種系統內部的錯誤,這種錯誤不像Exception一樣是可能是程序和業務上的錯誤是可以恢復的。
假設進行網絡連接操作的時候,IOException 發生了,可能是網絡中斷,我可以再嘗試幾次。
假設OutOfMemoryError發生了,就算被捕捉了,可以有什么手段讓程序正常運行下去嗎? 假設ExceptionInInitializerError發生了,類無法被正常初始化,這個是可以通過捕捉來恢復的嗎?