1. 程式人生 > >很多程式設計師多年都沒掌握的異常處理技巧和原則

很多程式設計師多年都沒掌握的異常處理技巧和原則

Java中的異常機制是指:當程式在執行過程中遇到意外情況時會自動丟擲一個Exception物件來通知程式,程式收到這個異常通知後可以採取各種處理措施,這種機制能使程式更加健壯,可讀性更高。本文就來講講異常處理的相關知識。


異常分類

Java中的異常分為RuntimeException和CheckedException。

  • RuntimeException:程式執行過程中出現錯誤,才會被檢查的異常。例如:型別錯誤轉換,陣列下標訪問越界,空指標異常、找不到指定類等等。
  • CheckedException:來自於Exception且非執行時異常都是檢查異常,編譯器會強制檢查並通過try-catch塊來對其捕獲,或者在方法頭宣告丟擲該異常,交給呼叫者處理。常見的checked異常有FileNotFoundException
    InterruptedException等。

Error和Exception的區別

在談到Exception時,經常會涉及到Error。Error和Exception存在如下的區別:

Error是指系統中的錯誤,程式設計師是不能改變和處理的,是在程式執行時出現的錯誤,只能通過修改程式才能修正。Java中的Error一般是指與虛擬機器相關的問題,如系統崩潰,虛擬機器錯誤,記憶體空間不足,方法呼叫棧溢位等。對於這類錯誤的導致的應用程式中斷,僅靠程式本身無法恢復和和預防,遇到這樣的錯誤,建議讓程式終止,調整程式碼或者虛擬機器引數再重新啟動程式;

Exception(異常)是程式可以處理的。遇到這類異常,程式設計師應該儘可能捕獲處理異常,使程式恢復執行,而不應該隨意終止異常。實在不知道如何處理就向上丟擲該異常留給呼叫者處理。

總結下:異常(Exception)是一種非程式原因的操作失敗(Failure),而錯誤(Error)則意味著程式有缺陷(Bug)。

異常處理的原則

1. 能處理的異常儘早處理
對於能明確知道要怎麼處理的異常要第一時間處理掉。對於不知道要怎麼處理的異常,要麼直接向上丟擲,要麼轉換成RuntimeException再向上丟擲,讓呼叫者處理。

2. 具體明確原則
儘量不要用Exception捕獲丟擲所有異常。丟擲異常時需要針對具體問題來丟擲異常,丟擲的異常要足夠具體詳細;在捕獲異常時需要對捕獲的異常進行細分,這時會有多個catch語句塊,這幾個catch塊中間泛化程度越低的異常需要越放在前面捕獲,泛化程度高的異常捕獲放在後面,這樣的好處是如果出現異常可以近可能得明確異常的具體型別是什麼。

丟擲時:


public FileInputStream(File file) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }
    //根據不同的情況丟擲不同的異常
    if (name == null) {
        throw new NullPointerException();
    }
    if (file.isInvalid()) {
        throw new FileNotFoundException("Invalid file path");
    }
    ...
}

捕獲時:

public void foo1(String fileName){
    File file = new File(fileName);
    InputStream in = null;
    try {
        in = new FileInputStream(file);
        Integer num = in.read();
        in.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e){
        e.printStackTrace();
    } catch(Exception e){
        e.printStackTrace();
    }
}

3. 系統需要有統一異常處理機制
系統需要有自己的一套統一異常處理的機制,如果系統使用Spring等框架的話可以非常簡單地引入統一異常處理框架。另外在統一異常處理時一定要打出異常堆疊,不然的話問題可能就無從查起了。

4. 一些其他注意點

  • try語句塊內要分清穩定程式碼和非穩定程式碼,對於穩定的不會出現異常的程式碼不要放到try語句塊中;
  • catch捕獲的異常一定要處理,吃掉異常不處理的話將是滅頂之災;
  • finally中不要使用return語句,因為finally語句塊最後一定會執行,這裡的return語句會覆蓋之前的return語句。

如何自定義異常

在複雜業務環境下,java自帶的異常可能滿足不了我們業務的需求, 這個時候我們可以自定義異常來進行對業務異常的處理。

public class MyException extends RuntimeException {

    private static final long serialVersionUID = 6958499248468627021L;
   
    private String errorCode;
    
    private String errorMsg;

    public MyException(String errorCode,String errorMsg){
        super(errorMsg);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public MyException(String errorCode,String errorMsg,Throwable throwable){
        super(errorCode,throwable);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

在使用MyException時,最好定義一個列舉類用來列舉錯誤程式碼和錯誤詳情。

return和finally的執行順序

在Java中,異常處理主要由try、catch、throw、throws和finally5個關鍵字處理。Java程式中如果出現了異常,而且沒有被try-catch塊捕獲的話,系統會把異常一直往上層拋,直到遇到處理程式碼。如果一直沒有處理塊,就拋到最上層,如果是多執行緒就由Thread.run()丟擲,如果是單執行緒就被main()丟擲。丟擲之後,如果是子執行緒,這個子執行緒就退出了。如果是主程式丟擲的異常,那麼這整個程式也就退出了。

一個比較常見的異常處理流程如下:

    try {
        //step1
        System.out.println("try...");
        throw new RuntimeException("異常1...");
    }catch (Exception e){
        //step2
        System.out.println("catch。。。");
    }finally {
        //step3
        System.out.println("finally。。。");
    }
    //step4
    System.out.println("end...");

上述的程式碼中由於丟擲的異常被順利catch住了,所以當前執行緒不會結束,程式會繼續往下執行,step4這步程式碼會被打印出來。

    try {
        System.out.println("try...");
        throw new RuntimeException("異常1...");
    }catch (Exception e){
        System.out.println("catch。。。");
        throw new RuntimeException("異常2...");
    }finally {
        System.out.println("finally。。。");
    }
    System.out.println("end...");

上面的程式碼中,由於catch塊中又丟擲了一個異常,而這個異常沒有相應的catch塊處理,所以系統會向上拋這個異常,最後的列印語句也就的不到執行。

try、catch、finally、throw和throws使用歸納

  • try、catch和finally都不能單獨使用,只能是try-catch、try-finally或者try-catch-finally。
  • try語句塊監控程式碼,出現異常就停止執行下面的程式碼,然後將異常移交給catch語句塊來處理,catch塊執行完之後程式碼還會繼續往下執行。
  • finally語句塊中的程式碼一定會被執行,常用於回收資源 。
  • throws:宣告一個異常,告知方法呼叫者。
  • throw :丟擲一個異常,至於該異常被捕獲還是繼續丟擲都與它無關。

還有一個比較重要的是return和finally的執行關係,可以參考下這篇部落格

異常鏈

在平時的開發中,常常會在捕獲一個異常後丟擲另外一個自定義異常,並且希望把異常原始資訊儲存下來,這被稱為異常鏈。我們在自定義異常時,只要提供一個接收throwable引數的建構函式即可:

public MyException(String errorCode,String errorMsg,Throwable cause){
    super(errorCode,cause);
    this.errorCode = errorCode;
    this.errorMsg = errorMsg;
}

try-with-resources

我們知道,在Java程式設計過程中,如果打開了外部資源(檔案、資料庫連線、網路連線等),我們必須在這些外部資源使用完畢後,手動關閉它們。因為外部資源不由JVM管理,無法享用JVM的垃圾回收機制,如果我們不在程式設計時確保在正確的時機關閉外部資源,就會導致外部資源洩露,緊接著就會出現檔案被異常佔用,資料庫連線過多導致連線池溢位等諸多很嚴重的問題。

  1. 傳統的資源關閉方式
//這種方式關閉資源,程式碼顯得比較臃腫
public static void main(String[] args) {
    FileInputStream inputStream = null;
    try {
        inputStream = new FileInputStream(new File("test"));
        System.out.println(inputStream.read());
    } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }
}
  1. try-with-resources方式關閉資源
public static void main(String[] args) {
    try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
        System.out.println(inputStream.read());
    } catch (IOException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}

將外部資源的控制代碼物件的建立放在try關鍵字後面的括號中,當這個try-catch程式碼塊執行完畢後,Java會確保外部資源的close方法被呼叫。程式碼是不是瞬間簡潔許多!當一個外部資源的控制代碼物件實現了AutoCloseable介面,JDK7中便可以利用try-with-resource語法更優雅的關閉資源,消除板式程式碼。

這種方式其實是一種語法糖,關於語法糖的詳細介紹可以我的部落格。

參考

  • https://zhidao.baidu.com/question/394591002.html
  • https://www.cnblogs.com/mingorun/p/8900449.html
  • https://www.cnblogs.com/winner-0715/p/7504657.html#_label2