天天看點

Java異常Java異常的分類

Java異常的分類

Java中的所有的異常都是

Throwable

的子類,在大的方面分為

Error

Exception

兩大類,

Exception

又可以分為非檢查異常(UncheckedException,繼承于

RuntimeException

)和檢查異常(CheckedException繼承于Exception,但不繼承于

RuntimeException

),如下圖所示:

Java異常Java異常的分類

Error

Error通常用于描述系統級别的錯誤,當出現系統級别的錯誤的時候,系統環境已經不健康了,是以Error不需要強制捕獲和聲明,也不強制處理.常見的Error有

OutOfMemoryError

,

StackOverflowError

,

InternalError

,

UnknownError

等.

平時寫代碼的時候應該盡量避免一些可避免的Error,例如OutOfMemoryError,在大檔案處理或使用緩存的時候容易出現.

非檢測異常(UncheckedException)

直接或間接繼承于

RuntimeException

以及其本身都屬于非檢測異常.

該類異常無需顯示捕捉和處理,适用于調用的代碼不能繼續執行,需要立即終止的情況.例如數組越界,調用null對象的執行個體方法或屬性,方法參數的檢測等.

出現非檢測異常的情況大部分情況是代碼寫挫了,此時記下日志即可.

常見的RuntimeException

  • NullPointerException

    : 空指針異常,當應用試圖在要求使用對象的地方使用了null時,抛出該異常.
  • 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

    :一個線程在等待、睡眠或其他方式占用時,并且該線程在活動之前或期間被中斷.
  • IOException

    :程式進行IO操作失敗或被中斷.
  • 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);
    }
}