Java異常的分類
Java中的所有的異常都是
Throwable
的子類,在大的方面分為
Error
和
Exception
兩大類,
Exception
又可以分為非檢查異常(UncheckedException,繼承于
RuntimeException
)和檢查異常(CheckedException繼承于Exception,但不繼承于
RuntimeException
),如下圖所示:
Error
Error通常用于描述系統級别的錯誤,當出現系統級别的錯誤的時候,系統環境已經不健康了,是以Error不需要強制捕獲和聲明,也不強制處理.常見的Error有
OutOfMemoryError
,
StackOverflowError
,
InternalError
,
UnknownError
等.
平時寫代碼的時候應該盡量避免一些可避免的Error,例如OutOfMemoryError,在大檔案處理或使用緩存的時候容易出現.
非檢測異常(UncheckedException)
直接或間接繼承于
RuntimeException
以及其本身都屬于非檢測異常.
該類異常無需顯示捕捉和處理,适用于調用的代碼不能繼續執行,需要立即終止的情況.例如數組越界,調用null對象的執行個體方法或屬性,方法參數的檢測等.
出現非檢測異常的情況大部分情況是代碼寫挫了,此時記下日志即可.
常見的RuntimeException
-
: 空指針異常,當應用試圖在要求使用對象的地方使用了null時,抛出該異常.NullPointerException
-
: 方法被傳入了非法或不适當的參數.IllegalArgumentException
-
: 下标越界異常,數組、字元串或者集合的索引超出了範圍。IndexOutOfBoundsException
-
: 非法狀态異常(表示在非法或不适當的時間調用了方法)IllegalStateException
-
: 算術條件異常.例如:整數除零.ArithmeticException
-
: 安全性異常.SecurityException
-
: 類型強制轉換異常.ClassCastException
當對方法的參數進行檢查時,參數值為null,此時應該抛出IllegalArgumentException還是NullPointerException?
參見: https://stackoverflow.com/questions/3881/illegalargumentexception-or-nullpointerexception-for-a-null-parameter
個人了解:雖然《Effective Java》和jdk源碼更傾向于抛出NullPointerException,但是不夠直覺,容易造成誤解,例如以下一段代碼;
//如果這行代碼報NullPointerException異常,我們首先會懷疑loginModule為null,而不是userName或者password為null.
loginModule.login(userName,password);
其次,
IllegalArgumentException
就是設計用于參數檢查的,不應該為了參數null的檢查,而引入其它類型的異常.
是以,參數檢查時,參數為null時,使用
會更加合理.
IllegalArgumentException
檢測異常(CheckedException)
檢測異常指的是繼承于
Exception
,但不繼承于
RuntimeException
的異常.
該類異常需要顯示使用catch進行捕捉,當catch到檢測異常時,可以選擇進一步的恢複或處理其它一些事情.
例如IOException,當catch到IOException以後,可以對io流進行及時的關閉.
常見的檢測異常
-
:當程式嘗試使用字元串的形式加載類時,但是未找到相應的類.ClassNotFoundException
-
:嘗試打開指定路徑的檔案失敗.FileNotFoundException
-
:一個線程在等待、睡眠或其他方式占用時,并且該線程在活動之前或期間被中斷.InterruptedException
-
:程式進行IO操作失敗或被中斷.IOException
-
:資料庫通路錯誤.SQLException
常見誤區
參見: https://www.ibm.com/developerworks/cn/java/j-lo-exception-misdirection/index.html
異常類型不夠明确
抛出的異常越明确越好,越明确的異常越能提供更多的資訊,抛出的異常最好需要攜帶message,用于對異常出現的原因進行描述,友善以後更準确的定位問題,例如下面參數的檢查:
public String md5(String data) {
if (data == null) {
// 這裡抛出的異常不夠具體,而且抛出的異常不含message.
throw new RuntimeException();
}
...
}
上面的代碼抛出的異常不夠具體,而且沒有message,應該為下面的形式:
public String md5(String data) {
if (data == null) {
throw new IllegalArgumentException("data is null.");
}
...
}
直接将異常顯示在頁面或者用戶端
實際上任何異常對使用者而言沒有實際意義,可以在異常中引入錯誤代碼,一旦出現異常,隻需要将異常的額代碼呈現給使用者,或者将錯誤代碼換成更通俗易懂的提示,開發人員也可以根據錯誤代碼去排查問題.
對代碼層次結構的污染
例如下面一段代碼:
public boolean insert(String name,String pwd)throws SQLException{
//将使用者名和密碼插入資料庫
}
上面的代碼将會污染到上層調用的代碼,我們可以改為下面一種形式:
public boolean insert(String name,String pwd){
try{
//将使用者名和密碼插入資料庫
}catch(SQLException e){
//利用非檢測異常封裝檢測異常,降低層次耦合
throw new RuntimeException(SQLErrorCode, e);
}finally{
//關閉連接配接,清理資源
}
}
忽略異常
例如下面的異常,僅僅是将異常輸出到控制台,沒有任何實際意義.而且代碼将會繼續執行,進而導緻其它無關的異常.
public boolean insert(String name,String pwd){
try{
//将使用者名和密碼插入資料庫
}catch(SQLException e){
//這裡直接将異常列印到控制台并沒有實際意義
//這裡可以将異常post到伺服器,也可以轉換為RuntimeException抛出.
e.printStacktrace();
}finally{
//關閉連接配接,清理資源
}
}
将異常包含在循環語句塊中
下面的代碼可能會導緻catch中的代碼塊執行多次.
for(int i=0; i<100; i++){
try{
}catch(XXXException e){
//...
}
}
另一種情況與上面的情況類似:A類循環調用B類中的某個方法,該方法塊中存在
try-catch
這樣的語句塊.
利用Exception捕捉所有潛在的異常
一段代碼塊中可能會抛出幾種不同類型的異常,可能為了代碼的簡潔,會使用基類Exception捕捉所有潛在的異常.這樣會丢失原有的異常詳細資訊;
public void retrieveObjectById(Long id){
try{
//…抛出 IOException 的代碼調用
//…抛出 SQLException 的代碼調用
}catch(Exception e){
//這裡利用基類 Exception 捕捉的所有潛在的異常,如果多個層次這樣捕捉,會丢失原始異常的有效資訊
throw new RuntimeException(“Exception in retieveObjectById”, e);
}
}
可以改為以下形式:
public void retrieveObjectById(Long id){
try{
//…抛出 IOException 的代碼調用
//…抛出 SQLException 的代碼調用
}catch(IOException e){
//僅僅捕捉 IOException
throw new RuntimeException(/*指定這裡 IOException 對應的錯誤代碼*/code,“Exception in retieveObjectById”, e);
}catch(SQLException e){
//僅僅捕捉 SQLException
throw new RuntimeException(/*指定這裡 SQLException 對應的錯誤代碼*/code,“Exception in retieveObjectById”, e);
}
}