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:…… ”,這回知道是什麼了吧。
學習路線、學習建議和免費經典程式設計書籍下載:
GitHub連結:https://github.com/yuantingyue/JavaLearningRoutes-Books
後續會不斷完善學習路線和學習資源,分享更多的知識和更多資源,一起成長,共勉?
想獲得最新訊息可關注WX公眾號:Java全棧佈道師
點贊走起?
原創不易,轉載指明出處!!!