你現在就必須知道的Java異常體系

對於異常情況,例如,可能造成程序崩潰的錯誤輸入,Java是通過捕獲機制來處理異常錯誤。當程序出錯時,我們不可能總是及時和用戶溝通,所以希望記錄出現的問題,以備日後進行分析。

如何處理錯誤

當一個用戶在運行程序期間,由於程序的錯誤或一些外部環境的影響造成用戶數據的丟失,用戶就有可能不再使用這個程序了。爲了避免這類事情發生,我們應該注意以下幾點。

  1. 向用戶通知錯誤的原因
  2. 保存所有的工作結果(日誌)
  3. 允許用戶以妥善的形式退出程序

異常分類

所有Java異常對象都是由Throwable繼承而來。如果Java內置的異常類不滿足我們的需求,我們可以創建自己的異常類。下面是我畫的Java異常結構的一個簡化示意圖。

Error         是描述Java運行時系統內部錯誤和資源耗盡錯誤。應用程序不應該拋出這種類型的對象。如果出現了這樣內部錯誤,除了告知客戶,並盡力使用程序安全地終止之外,別無他法,不過這樣的情況很少出現。
Exception                   是描述Java應用程序拋出和處理的非嚴重型錯誤,程序錯誤導致的異常就屬於RuntimeException;而程序本身沒有問題,但由於I/O錯誤這類問題導致的異常屬於其他異常。

派生於RuntimeException的異常包含下面幾種情況:

  • 錯誤的類型轉換
  • 數組下標越界
  • 空指針異常

不是派生於RuntimeException的異常(其他異常):

  • 在文件尾部後面讀取數據
  • 打開一個不存在的文件
  • 根據給定的字符串查找Class對象,而這個字符串表示的類不存在

如果存在RuntimeException異常,那麼就一定是你的問題這是一條非常有道理的規則,不知道大家認不認可,我們應該在變量使用前檢測是否爲null可以防止出現NullPointerException(空指針異常);通過檢測數組下標是否越界可以避免ArrayIndexOutOfBoundsException異常。

拋出異常

當我們需要在一個方法中拋出一個異常時,我們使用throw後加某異常類的實例,程序會在此向客戶端程序(調用這段代碼的程序)拋出對應異常並在此退出(相當於return)。另外需要注意的是,我們必須在定義該方法的時候指明異常類型,比如下面這段代碼會拋出testException異常。

public void demo() throws testException,AException,BException{
  throw new testException(); 
}

不同的異常類之間用逗號隔開即可,在這種情況下我們不必須throw每個異常類的實例(),但是客戶端代碼必須要catch到每個異常類:

public class MyException {
  public static void main(String[] args){
    MyException e = new MyException();
    try {
      e.demo();
    } catch (testException e1) {
      e1.printStackTrace();
    } catch (BException e1) {
      e1.printStackTrace();
    } catch (AException e1) {
      e1.printStackTrace();
    }
  }
 
  public void a() throws testException,AException,BException{
    throw new testException();
  }
}
 
class testException extends Exception {};
class AException extends Exception{};
class BException extends Exception{};

捕獲異常

1.捕獲單個異常

try {
  //code  more code
}catch (Exception e){
  //handler for this type
}

2.捕獲多個異常

注:捕獲多個異常不僅讓你代碼看起來更簡單,還會更加高效。

try {
  ...
}catch (FileNotFoundException e){
  ...
}catch (UnknownHostException e){
  ...
}catch (IOException e){
  ...
}

異常對象可能包含與異常本身有關的信息。要想獲取對象更多的信息,可以嘗試使用e.getMessage(),得到詳細的錯誤信息(如果有的話),或者使用e.getClass().getName()得到異常對象的實際類型。

3.再次拋出異常與異常鏈

在catch子句中可以拋出一個異常,這樣做是爲了改變異常的類型。如果我們開發了一個供其他程序使用的子系統,那麼表示子系統故障的異常類型可能會產生很多種解釋。ServletException就是如此,執行servlet代碼可能不想知道發生錯誤的細節,但希望明確知道servlet是否有問題。

try {
  //連接database
}catch (SQLException e){
  throw new ServeletException("database error:"+e.getMessage());//throw拋異常
}

這裏,ServeletException用帶有異常信息文本的構造器來構造。不過可以有一種更好的處理方法,並且將原始異常設置爲新異常的原因:

try {
      //連接database
}catch (SQLException e){
    Throwable se = new ServeletException("database error");
    se.initCause(e);
    throw se;
}

當捕獲到異常時,就可以用下面這條語句重新得到原始異常:

Throwable e = se.getCause();

強烈建議使用這種包裝技術。可以讓用戶拋出子系統的高級異常,而不會丟失原始異常的細節。

小提示:如果在一個方法中發生異常,而又不允許拋出它,那麼包裝技術就十分重要。我們可以捕獲這個異常,並將它包裝成一個運行時異常。比如:有時候你可能只想記錄一個異常,再將它重新拋出,而不做任何的改變,代碼示例如下:

try {
     //連接database
}catch (Exception e){
    logger.log(level,message,e);//日誌
    throw e;
}

4.finally子句

不管是否有異常被捕獲,finally子句中的代碼總是會被執行。當finally包含return時,將會出現意想不到的結果,假設從try中退出return。在方法返回前,finally中的內容將會被執行。如果finally中也有return,這個返回值會覆蓋原始的返回值,例子如下:

public static int(int n){
	try{
	    int r = n*n;
	    return r;
	}
	finally{
	    if(n==2) return 0;
	}
}

如果在finally中關閉資源,最好加上處理,防止異常,這是最傳統的做法。下面第五點將給大家介紹jdk7之後資源關閉的方式。

finally{
    try{
	in.close();
    }catch(Exception e){
	throw e;
    }
}

5.try-with-resource(帶資源的try)

如果打開了外部資源(文件、數據庫連接、網絡連接等),我們必須在這些外部資源使用完畢後,手動關閉它們。因爲外部資源不由JVM管理,無法享用JVM的垃圾回收機制,如果我們不在編程時確保在正確的時機關閉外部資源,就會導致外部資源泄露,緊接着就會出現文件被異常佔用,數據庫連接過多導致連接池溢出等諸多很嚴重的問題。

當try退出時,會自動調用close方法,去讀取單詞中的所有單詞,示例如下:

try(Scanner in = new Scanner(New FileInputStream("/user/share/dict/words")),"UTF-8"){
    while(in.hasNext()){	
	System.out.println(in.next());
    }
}

如果try拋出一個異常,而且close方法也拋出一個異常,這樣就會帶來一個難題。到資源的try可以很好地處理。原來的close方法拋出的異常被抑制。這些異常將會自動捕獲,並由addSuperssed方法增加到原來的異常。如果對這些異常感興趣,可以調用getSupperssed方法,它會得到從close拋出並抑制的異常列表。

自定義異常

有時候Java內置異常不滿足於我們的需求,我們就需要用到自定義異常。比如:系統中有些錯誤是符合Java語法的,但不符合我們項目的業務邏輯;企業項目是分模塊或者分功能開發的 ,使用自定義異常類就統一了對外異常展示的方式等。

所有異常都必須是 Throwable 的子類。
如果希望寫一個檢查性異常類,則需要繼承 Exception 類。
如果你想寫一個運行時異常類,那麼需要繼承 RuntimeException 類。

創建一個檢查性異常類

public class DatamartAssertException extends Exception {
	
    private static final long serialVersionUID = 7358625131054183395L;
	
    public DatamartAssertException() {
		
    }	
	
    public DatamartAssertException(String message) {  
    	super(message);
    }  	
}

拋出和使用自定義異常

public static void main(String[] args) throws DatamartAssertException{
        CloseableHttpClient httpClient = null;
        try{
            //創建httpClient實例
            httpClient  = HttpClients.createDefault();
            //地址url
            String url = "www.baidu.coms";
            //創建HttpPost實例
            HttpPost method = new HttpPost(url);
            //設置請求讀取超時時間 60秒
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(60000).setConnectionRequestTimeout(60000)
                    .setSocketTimeout(60000).build();
            method.setConfig(requestConfig);
            //執行請求
            try{
                HttpResponse result = httpClient.execute(method);
                /**請求發送成功,並得到響應**/
                if (result.getStatusLine().getStatusCode() == 200) {
                    String str = "";
                    /**讀取服務器返回過來的json字符串數據**/
                    str = EntityUtils.toString(result.getEntity());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }catch(ConnectTimeoutException e){
            // 捕獲超時異常 並反饋給調用者
            throw new DatamartAssertException("請求超時!");
        }catch(ConnectException e) {
            throw new DatamartAssertException("請求超時!");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally{
            /**關閉連接,釋放資源**/
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

總結:不要過分的細化異常,不要壓制異常;早拋出,晚捕獲。

 

--------------如果大家喜歡我的博客,可以點擊左上角的關注哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章