Java核心技術梳理-異常處理

1、引言

異常老是不可避免的,就算咱們自身的代碼足夠優秀,但卻不能保證用戶都按照咱們想法進行輸入,就算用戶按照咱們的想法進行輸入,咱們也不能保證操做系統穩定,另外還有網絡環境等,不可控因素太多,異常也不可避免。java

但咱們能夠經過異常處理機制讓程序有更好的容錯性和兼容性,當程序出現異常時,系統自動生成Exception對象通知系統,從而將業務功能實現代碼和錯誤處理代碼分離。sql

異常處理已經成爲衡量一門語言是否成熟的標誌之一,增長了異常處理機制後程序有更好的健壯性和容錯性。數據庫

2、異常處理機制

咱們但願系統有一種機制可以將錯誤處理代碼和正常實現代碼分離開,至關於一個if else,if中爲正常實現代碼,而else爲錯誤的處理。安全

2.1 try...catch 捕獲異常

咱們按照上面的思路,java利用try catch來進行異常捕獲。網絡

try{
    //業務代碼
}
catch(IOException ex){
    //錯誤處理    
}
catch(Exception ex){
    //錯誤處理代碼
}

當try塊代碼出錯時,系統生成一個異常對象,並將對象拋給運行環境,這個過程叫作拋出異常,運行環境接收到異常對象是,會尋找處理該異常對象的catch代碼塊,找到合適的catch塊,就將對象給其處理,若是找不到,則運行環境終止,程序也將退出。性能

2.2 異常類繼承體系

Java提供了豐富的異常類,這些異常類有嚴格的繼承關係優化

 

從這個圖能夠看出異常主要分爲兩類,Error與Exception,Error錯誤通常是指與虛擬機相關的問題,如系統崩潰、虛擬機錯誤,這些錯誤沒法恢復或不可能捕獲,將致使應用程序崩潰,這些不須要咱們去捕獲。spa

在捕獲異常時咱們一般把Exception類放在最後,由於按照異常捕獲的機制,從上至下判斷該異常對象是不是catch中的異常類或其異常子類,一旦比較成功則用此catch進行處理。若是將Exception類放在前面,那麼就會進行直接進入其中,由於Exception類是全部異常類的父類,那排在它後面的異常類將永遠得不到執行的機會,這種機制咱們稱爲先小後大操作系統

2.3 多異常捕獲

Java 7 開始,一個catch塊中能夠捕獲多種類型的異常:日誌

public static void main(String[] args) {
    try {
        Integer a = Integer.parseInt(args[0]);
        Integer b = Integer.parseInt(args[1]);
        Integer c = a / b;
        System.out.println(c);

    } catch (IndexOutOfBoundsException | NumberFormatException
            | ArithmeticException ie) {
        //異常變量默認final,不能從新賦值
        ie = new ArithmeticException("text");

    } catch (Exception ex) {

    }
}

多個異常之間用豎線(|)隔開,而且異常變量默認final,不能從新賦值。

2.4 獲取異常信息

異常捕獲後咱們想要查看異常信息,能夠經過catch後的異常形參來得到,經常使用的方法以下:

  • getMessage():返回異常的詳細描述字符串。

  • getStackTrace():返回異常跟蹤棧信息。

  • printStackTrace():將異常跟蹤棧信息按照標準格式輸出。

  • printStackTrace(PrintStream p):將異常跟蹤棧信息輸出到指定輸出流。

2.5 finally回收資源

在try裏面打開的一些物理鏈接,須要顯示的回收,但顯然在try裏面或者catch裏面進行回收是不行的,try中一旦出現異常,回收可能不會執行到,而catch更不行,由於在沒有異常時根本不會執行,因而異常處理機制提供了finally,finally塊代碼必定會被執行,便是try中執行了return語句也仍是會執行。

try{
    //業務代碼
}catch(XXXException xx){
    //異常處理
}catch(XXXException xx){
    
}finally{
    //資源回收
}

在異常處理中,try是必須的,沒有try塊,後面的catch和finally沒有意義,catch和finally必須出現一個,finally塊必須是最後。

若是try塊中有return語句,則會先執行finally,而後再執行return語句,若是try塊中有exit語句,則不會執行finally,都直接退出虛擬機了固然不會再去執行。

2.6 自動關閉的try語句

每次都須要資源回收,顯得有些麻煩,Java還支持一種寫法,直接在try後申明資源,那麼就能夠替換掉finally,但這個資源必須實現Closeable或者AutoCloseable接口

try (
        BufferedReader bufferedReader = new BufferedReader(new FileReader(""))
) {
    bufferedReader.read();
}

3、Checked異常與Runtime異常

Java的異常分爲兩大類:Checked(可檢查)異常和Runtime(運行時)異常,全部的RuntimeException類及其子類的實例就是Runtime異常,其餘的都是Checked異常。

對於Checked異常處理方式有兩種,一種是明確知道如何處理該異常,用try catch來捕獲異常,而後在catch中修復異常,一種是不知道如何處理,在定義方法時申明拋出異常。

Runtime異常無需顯示申明拋出,須要捕獲異常,就用try catch來實現。

3.1 使用throws聲明拋出異常

使用throws聲明的思路是:當前方法不知道如何處理這種類型的異常,則由上一級調用者處理,若是main方法也不知道如何處理,也可使用throws拋給JVM,JVM的處理是,打印異常的跟蹤棧信息,並終止程序。

throws聲明拋出只能在方法簽名中使用,能夠聲明拋出多個異常類,多個異常類用逗號隔開,如:

public static void main(String[] args) throws IOException {
    FileInputStream fileInputStream = new FileInputStream("");
}

申明瞭throws就不須要再使用try catch來捕獲異常了。

若是某段代碼中調用了一個帶throws聲明的方法,那麼必須用try catch來處理或者也帶throws聲明,以下例子:

public static void main(String[] args) {
    try {
        test();
    } catch (IOException e) {
        e.printStackTrace();
    }

}
public static void test () throws IOException{
    FileInputStream fileInputStream = new FileInputStream("");
}

這個時候要注意,子類方法聲明拋出的異常應該是父類方法聲明拋出異常的子類活相同,不容許比父類聲明拋出的異常多。

本人以前是C#開發,C#開發沒有Checked異常,我的以爲這個Checked異常有些繁瑣,它最大的做用彷彿是提醒開發處理異常,避免由於粗心而忘記處理異常,不知道是否是理解有誤。

4、使用throw拋出異常

程序出現錯誤,系統會拋出異常,有時候咱們也想自行拋出異常,好比用戶未登陸,咱們可能就直接拋出錯誤,這種自行拋出的異常通常都與業務相關,由於業務數據與既定不符,可是這種異常並非一種錯誤,系統不會捕捉,就須要咱們自行拋出。

使用throw語句進行異常拋出,拋出的不是一個異常類,而是一個異常實例,並且每次只能拋出一個:

if (user== null) {
    throw new Exception("用戶不存在");
}

這裏咱們又要區分Checked異常與運行時異常,運行時異常申明很是簡單,直接拋出便可,而Checked異常又要像以前同樣,要麼使用try catch,要麼聲明throws

public static void main(String[] args) {
    try {
        //檢查時異常須要寫try catch
        test1();
    } catch (Exception e) {
        e.printStackTrace();
    }
    //運行時異常直接調用便可
    test2();

}
public static void test1() throws Exception {
    if (1 > 0) {
        throw new Exception("用戶不存在");
    }
}
public static void test2() {
    if (1 > 0) {
        throw new RuntimeException("用戶不存在");
    }
}

4.1 自定義異常類

爲了讓錯誤更直觀的表達信息,有時候咱們須要自定義異常, 自定義異常很簡單,只須要繼承Exception或者RuntimeException基類,通常會提供兩個構造器,一個空構造器,一個帶有描述信息的構造器:

public class GlobalException extends RuntimeException {

    //無參構造器
    public GlobalException() {
    }

    //帶有錯誤描述信息的構造器
    public GlobalException(String msg) {
        super(msg);
    }
}

4.2 異常鏈

在實際開發中,咱們通常會分層開發,比較經常使用的是三層,表現出、業務邏輯層、數據庫訪問層,咱們不會拋出數據庫異常給用戶,由於這些異常中有堆棧信息,很不安全,也很是的不友好。

一般,咱們捕獲原始異常(能夠寫入日誌),而後再拋出一個業務異常(一般是自定義的異常),這個業務異常能夠提示用戶異常的緣由:

public void update() throws GlobalException{
    try{

        //執行sql
    }

    catch (SQLException ex){
        //記錄日誌
        ...
            //拋出自定義錯誤
            throw new GlobalException("數據庫報錯");
    }
    catch (Exception ex){
        //記錄日誌
        ...
            throw new GlobalException("未知錯誤!");
    }
}

這種捕獲一個異常而後拋出另外一個異常,並將原始信息保存起來的是一種典型的鏈式處理(責任鏈模式)。

5、異常處理規則

異常給系統帶來了健壯性和容錯性,可是使用異常處理並不是如此簡單,咱們還要注意性能和結構的優化,有些規則咱們必須瞭解,而這些規則的主要目標是:

  • 程序代碼混亂最小化。

  • 捕獲並保留診斷信息。

  • 通知合適的人員

  • 採用合適的方式結束異常。

5.1 不要過分使用異常

什麼叫過分使用異常呢?有兩種狀況,一是把異常和普通錯誤放在一塊兒,使用異常來代替錯誤,什麼意思呢?就是對一些咱們已知或可控的錯誤進行異常處理,如一些業務邏輯判斷,用戶的輸入等,並非只有直接拋出異常這種選擇,咱們能夠直接經過業務處理進行錯誤返回,而不是拋出錯誤,拋出錯誤的效率要低一些,只有對外部的、不能肯定和預知的運行時錯誤使用異常。

二就是使用異常來代替流程控制,異常處理機制的初衷是將不能夠預期的錯誤和正常的業務代碼分離,不該該用異常來進行流程控制。

5.2 不要使用過大的try塊

不要把大量的業務代碼放在try中,大量的業務代碼意味着錯誤可能性也增大,也意味着一旦出錯,分析錯誤的複雜度也增長,並且try中包含大量業務,可能後面緊跟的catch塊也不少,咱們會使用多個catch來捕獲錯誤,這樣代碼也很臃腫,應該儘可能細分try,去分別捕獲並處理。

5.3 不要忽略捕獲到的異常

不要忽略異常,當咱們捕獲到異常時,咱們不要去忽略它,若是在catch中什麼也不作,那是一種恐怖的作法,由於這意味着出現了錯誤咱們並不知道(極特殊的狀況例外,好比:一些可重試的業務處理),最起碼的作法是打印錯誤日誌,更進一步看是否能夠修復錯誤,或者向上拋出錯誤。

相關文章
相關標籤/搜索