JAVA異常處理

淵渟嶽發表於2022-02-24

1.異常概述

異常的定義:異常顧名思義是不同於平常的,異常情況是不正常的情況,異常程式指的是非正常想要的程式。

假設沒有異常處理機制,當程式出現非正常情況時,程式便會直接結束(因為無法繼續執行程式列印日誌,所以是什麼原因導致程式崩潰都不知道);有異常處理時,當程式出現非正常情況時,可以捕獲這異常資訊並做處理(比如列印錯誤日誌),再看業務情況是否繼續執行或結束程式。所以“異常”是屬於一種可預測的正常情況。

異常的作用:異常機制可以使程式中的異常處理程式碼和正常業務程式碼分離,保證程式程式碼更加優雅、有更好的容錯和更加健壯。

2.異常的分類(異常繼承體系)

分為兩大類:錯誤和異常(編譯時異常和執行時異常)

列出常見的幾個錯誤和異常型別。錯誤和異常的種類很多,Java程式語言所涉及的能力中都有相應的異常類,如有不懂可以檢視原始碼的類註釋。

3.異常的使用

3.1.異常處理

程式語言的異常處理基礎能力已經成為一門成熟程式語言的標準,除傳統的像C語言沒有提供異常機制之外,目前主流的程式語言如Java、C++、Python、Go、Ruby等都具備了成熟的異常機制。

異常的處理:丟擲異常(提出問題)和 捕獲並處理異常(解決問題)。當程式出現當前環境無法處理產生的問題時,便向上一級丟擲問題,尋找能解決問題的環境,直到找到含有合適異常處理的方法並執行,未找到則終止程式。

Java 異常機制的五個關鍵字:try 、catch 、finally 、throw 和throws。

  • try關鍵字後緊跟一個花括號擴起來的程式碼塊(花括號不可省略),用於監聽可能引發異常的程式碼;
  • catch關鍵字後異常型別和一個異常處理程式碼塊,捕獲某一型別的異常和處理這種型別的程式碼塊。catch塊可以有多個,表示捕獲不同型別異常和處理;
  • finally關鍵字位於catch塊後,用於回收在try塊裡開啟的物理資源,異常機制會保證finally塊一定會被執行;
  • throws關鍵字在方法簽名中使用,用於宣告該方法可能丟擲的異常;
  • throw關鍵字用於丟擲一個實際的異常物件,throw可以單獨作為語句使用。

throw與throws的區別

throws:在方法宣告時使用,宣告可能會丟擲的一個或多個異常。

throw:在方法內使用,手動丟擲一個異常物件;

throw與throws的關係

throw在方法內手動丟擲一個異常物件,此時在方法宣告中必須使用throws丟擲該異常或其父類異常。throw是丟擲異常物件,throws是告訴使用者這個方法可能有這個異常。

3.2.異常的處理流程

3.3.異常實戰

try……catch

try……finally

try……catch……finally

try……catch……catch……finally

案例一

// 檢測
try{
    System.out.println("檢測是否有異常");
    float a = 1/0;
    System.out.println("出現異常後,我還可以執行嗎");
}
// 捕獲和處理
catch (Exception e){
    e.printStackTrace();
    System.out.println("捕獲和處理異常");
}
System.out.println("繼續執行嗎?");

 執行結果:

檢測是否有異常 捕獲和處理異常 繼續執行嗎? java.lang.ArithmeticException: / by zero

結論:當出現異常後,catch捕獲處理異常情況,在try塊中異常程式碼後面的程式碼無法繼續執行,但程式可以繼續正常執行後續程式碼。

案例二

// 檢測
try{
    System.out.println("檢測是否有異常");
    float a = 1/0;
    System.out.println("出現異常後,我還可以執行嗎");
}
// 結束
finally{
    System.out.println("做什麼都要帶上我");
}
System.out.println("繼續執行嗎?");

執行結果:

檢測是否有異常 做什麼都要帶上我 Exception in thread "main" java.lang.ArithmeticException: / by zero

結論:當出現異常後,不catch,只finally,在try塊中異常程式碼後面的程式碼無法繼續執行,並且程式無法正常執行後續程式碼。

案例三

// 檢測
try{
    System.out.println("檢測是否有異常");
    float a = 1/0;
    System.out.println("出現異常後,我還可以執行嗎");
}
// 捕獲和處理
catch (ArithmeticException e){
    e.printStackTrace();
    System.out.println("捕獲和處理異常");
}
// 結束
finally{
    System.out.println("做什麼都要帶上我");
}
System.out.println("繼續執行嗎?");

執行結果:

檢測是否有異常 捕獲和處理異常 做什麼都要帶上我 繼續執行嗎? java.lang.ArithmeticException: / by zero

結論:當出現異常後,catch捕獲處理異常情況,在try塊中異常程式碼後面的程式碼無法繼續執行,但程式可以繼續正常執行後續程式碼,並且finally的程式碼正常執行。

案例四

// 檢測
try{
    System.out.println("檢測是否有異常");
    float a = 1/0;
    System.out.println("出現異常後,我還可以執行嗎");
}
// 捕獲和處理
catch (NullPointerException e){
    e.printStackTrace();
    System.out.println("捕獲和處理異常:NullPointer");
}
// 捕獲和處理
catch (ArithmeticException e){
    e.printStackTrace();
    System.out.println("捕獲和處理異常:Arithmetic");
}
// 結束
finally{
    System.out.println("做什麼都要帶上我");
}
System.out.println("繼續執行嗎?");

執行結果:

檢測是否有異常 捕獲和處理異常:Arithmetic 做什麼都要帶上我 繼續執行嗎? java.lang.ArithmeticException: / by zero

結論:可以使用多個catch塊,只有捕獲到異常的catch才會執行。

驗證try和catch塊中有return 的情況

正常情況 return

public static String processEx(){

    try{
        System.out.println("檢測是否有異常");
        return "try return";
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("處理異常");
        return "catch return";
    }finally{
        System.out.println("做什麼都要帶上我");
    }
}

public static void main(String[] args){
    String s = processEx();
    System.out.println(s);
}

執行結果:

檢測是否有異常 做什麼都要帶上我 try return

結論:執行順序是try……finally……return;和前面測試一樣無異常不進入catch塊。

異常情況 return

public static String processEx(){

    try{
        System.out.println("檢測是否有異常");
        float a = 1/0;
        System.out.println("出現異常後,我還可以執行嗎");
        return "try return";
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("處理異常");
        return "catch return";
    }finally{
        System.out.println("做什麼都要帶上我");
    }
}

public static void main(String[] args){
    String s = processEx();
    System.out.println(s);
}

執行結果:

檢測是否有異常 處理異常 做什麼都要帶上我 catch return java.lang.ArithmeticException: / by zero

結論:執行順序是try……catch……finally……return;和前面測試一樣,try塊異常後不在執行後續程式碼。

注意:細心點會發現,這兩個案例都不需要在finally中return。因為finally 六親不認啥都有它分,不建議在finally中return,如果要統一return,那麼可以在finally{} 後編寫。

throw在try和catch中的使用

public static String processEx() {

    try{
        System.out.println("檢測是否有異常");
        throw new Exception("隨便甩個鍋");
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("這鍋我不背");
        return "catch return";
    }finally{
        System.out.println("我也不背");
    }
}

public static void main(String[] args) {
    String s = processEx();
    System.out.println(s);
}

執行結果:

檢測是否有異常 這鍋我不背 我也不背 catch return java.lang.Exception: 隨便甩個鍋

結論:throw 類似return,有throw後不能再使用return;try只要產生異常,catch 都會嘗試捕獲處理,這個案例是剛好catch可以處理這個異常,所以不需要在方法宣告中往外拋異常,如果catch無法處理將無法通過編譯,必須要求在方法宣告中使用throws丟擲才行,這也體現了編譯時異常的好處。

更多的情況可以自行驗證!

4.自定義異常

異常三部曲:建立異常類、丟擲異常和處理異常(檢測(try)、捕獲和處理(catch)和結束(finally)),在自定義異常中講解。

建立異常類:對問題進行抽象,如:檔名大小限制異常

public class DemoException extends  Exception{
    // 因為爺爺(Throwable)做了序列化
    private static final long serialVersionUID = 1L;

    public DemoException(String massage,Throwable cause){
        // 最終是通過本地方法 fillInStackTrace(0) 來獲得堆疊資訊;
        super(massage,cause);
    }

    // 方便異常類使用
    public DemoException(String massage){
        super(massage,null);
    }
}

丟擲異常 

// 丟擲異常
public static void testEx() throws DemoException {
    System.out.println("測試Demo異常");
    throw new DemoException("Demo異常");// 丟擲異常時附上的資訊
}

 處理異常

public static void main(String[] args) {
    try{
        System.out.println("檢測是否有異常");
        testEx();
    }catch (DemoException e){
        e.printStackTrace();
        System.out.println("處理異常");
    }finally{
        System.out.println("我想體現我的價值");
    }
}

5.異常鏈

異常鏈顧名思義是串聯在一起的異常資訊。A異常拋給B,B處理了A異常然後丟擲了A1異常,C接到A1異常處理了A1然後丟擲了A2異常……。

舉個例子

public class TestDemo2 {

    public static void processEx() throws Throwable {

        try{
            System.out.println("檢測是否有異常");
            throw new Exception("隨便甩個鍋1");
        }catch (Exception e){
//            e.printStackTrace();
            System.out.println("處理異常:這鍋我不背");
            throw new Exception("隨便甩個鍋2").initCause(e);
        }finally{
            System.out.println("我也不背");
        }
    }

    public static void main(String[] args) {
        try{
            processEx();
        }catch (Throwable e){
            e.printStackTrace();
//            e.getCause();
        }
    }
}

執行結果:

在程式設計時經常看到 “Caused by:…… ”,這回知道是什麼了吧。

 

 

Java全棧學習路線、學習資源和麵試題一條龍

學習路線、學習建議和免費經典程式設計書籍下載:

GitHub連結:https://github.com/yuantingyue/JavaLearningRoutes-Books

後續會不斷完善學習路線和學習資源,分享更多的知識和更多資源,一起成長,共勉?

想獲得最新訊息可關注WX公眾號:Java全棧佈道師

點贊走起?

原創不易,轉載指明出處!!!

相關文章