一文讀懂 JAVA 異常處理

 

 

JAVA 異常類型結構

Throwable 是全部異常類型的基類,Throwable 下一層分爲兩個分支,Error 和 Exception.java

圖片描述

Error 和 Exeption

  • Error程序員

    Error 描述了 JAVA 程序運行時系統的內部錯誤,一般比較嚴重,除了通知用戶和盡力使應用程序安全地終止以外,無能爲力,應用程序不該該嘗試去捕獲這種異常。一般爲一些虛擬機異常,如 StackOverflowError 等。面試

  • Exception數據庫

    Exception 類型下面又分爲兩個分支,一個分支派生自 RuntimeException,這種異常一般爲程序錯誤致使的異常;另外一個分支爲非派生自 RuntimeException 的異常,這種異常一般是程序自己沒有問題,因爲像 I/O 錯誤等問題致使的異常,每一個異常類用逗號隔開。api

受查異常和非受查異常

  • 受查異常數組

    受查異常會在編譯時被檢測。若是一個方法中的代碼會拋出受查異常,則該方法必須包含異常處理,即 try-catch 代碼塊,或在方法簽名中用 throws 關鍵字聲明該方法可能會拋出的受查異常,不然編譯沒法經過。若是一個方法可能拋出多個受查異常類型,就必須在方法的簽名處列出全部的異常類。安全

    經過 throws 關鍵字聲明可能拋出的異常函數

    private static void readFile(String filePath) throws IOException { File file = new File(filePath); String result; BufferedReader reader = new BufferedReader(new FileReader(file)); while((result = reader.readLine())!=null) { System.out.println(result); } reader.close(); } 

    try-catch 處理異常組件化

    private static void readFile(String filePath) { File file = new File(filePath); String result; BufferedReader reader; try { reader = new BufferedReader(new FileReader(file)); while((result = reader.readLine())!=null) { System.out.println(result); } reader.close(); } catch (IOException e) { e.printStackTrace(); } } 
  • 非受查異常性能

    非受查異常不會在編譯時被檢測。JAVA 中 Error 和 RuntimeException 類的子類屬於非受查異常,除此以外繼承自 Exception 的類型爲受查異常。

異常的拋出與捕獲

直接拋出異常

一般,應該捕獲那些知道如何處理的異常,將不知道如何處理的異常繼續傳遞下去。傳遞異常能夠在方法簽名處使用 throws 關鍵字聲明可能會拋出的異常。

private static void readFile(String filePath) throws IOException { File file = new File(filePath); String result; BufferedReader reader = new BufferedReader(new FileReader(file)); while((result = reader.readLine())!=null) { System.out.println(result); } reader.close(); } 

封裝異常再拋出

有時咱們會從 catch 中拋出一個異常,目的是爲了改變異常的類型。多用於在多系統集成時,當某個子系統故障,異常類型可能有多種,能夠用統一的異常類型向外暴露,不需暴露太多內部異常細節。

private static void readFile(String filePath) throws MyException { try { // code } catch (IOException e) { MyException ex = new MyException("read file failed."); ex.initCause(e); throw ex; } } 

捕獲異常

在一個 try-catch 語句塊中能夠捕獲多個異常類型,並對不一樣類型的異常作出不一樣的處理

private static void readFile(String filePath) { try { // code } catch (FileNotFoundException e) { // handle FileNotFoundException } catch (IOException e){ // handle IOException } } 

同一個 catch 也能夠捕獲多種類型異常,用 | 隔開

private static void readFile(String filePath) { try { // code } catch (FileNotFoundException | UnknownHostException e) { // handle FileNotFoundException or UnknownHostException } catch (IOException e){ // handle IOException } } 

自定義異常

習慣上,定義一個異常類應包含兩個構造函數,一個無參構造函數和一個帶有詳細描述信息的構造函數(Throwable 的 toString 方法會打印這些詳細信息,調試時頗有用)

public class MyException extends Exception { public MyException(){ } public MyException(String msg){ super(msg); } // ... }​​ 

try-catch-finally


當方法中發生異常,異常處以後的代碼不會再執行,若是以前獲取了一些本地資源須要釋放,則須要在方法正常結束時和 catch 語句中都調用釋放本地資源的代碼,顯得代碼比較繁瑣,finally 語句能夠解決這個問題。

private static void readFile(String filePath) throws MyException { File file = new File(filePath); String result; BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(file)); while((result = reader.readLine())!=null) { System.out.println(result); } } catch (IOException e) { System.out.println("readFile method catch block."); MyException ex = new MyException("read file failed."); ex.initCause(e); throw ex; } finally { System.out.println("readFile method finally block."); if (null != reader) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } 

調用該方法時,讀取文件時若發生異常,代碼會進入 catch 代碼塊,以後進入 finally 代碼塊;若讀取文件時未發生異常,則會跳過 catch 代碼塊直接進入 finally 代碼塊。因此不管代碼中是否發生異常,fianlly 中的代碼都會執行。

若 catch 代碼塊中包含 return 語句,finally 中的代碼還會執行嗎?將以上代碼中的 catch 子句修改以下

catch (IOException e) { System.out.println("readFile method catch block."); return; } 

調用 readFile 方法,觀察當 catch 子句中調用 return 語句時,finally 子句是否執行

圖片描述

可見,即便 catch 中包含了 return 語句,finally 子句依然會執行。若 finally 中也包含 return 語句,finally 中的 return 會覆蓋前面的 return.

try-with-resource

上面例子中,finally 中的 close 方法也可能拋出 IOException, 從而覆蓋了原始異常。JAVA 7 提供了更優雅的方式來實現資源的自動釋放,自動釋放的資源須要是實現了 AutoCloseable 接口的類。

private static void tryWithResourceTest(){ try (Scanner scanner = new Scanner(new FileInputStream("c:/abc"),"UTF-8")){ // code } catch (IOException e){ // handle exception } } 

try 代碼塊退出時,會自動調用 scanner.close 方法,和把 scanner.close 方法放在 finally 代碼塊中不一樣的是,若 scanner.close 拋出異常,則會被抑制,拋出的仍然爲原始異常。被抑制的異常會由 addSusppressed 方法添加到原來的異常,若是想要獲取被抑制的異常列表,能夠調用 getSuppressed 方法來獲取。

阿里巴巴異常處理規約

  • 【強制】 Java 類庫中定義的能夠經過預檢查方式規避的 RuntimeException 異常不該該經過
    catch 的方式來處理,好比: NullPointerException, IndexOutOfBoundsException 等等。
    說明: 沒法經過預檢查的異常除外,好比,在解析字符串形式的數字時,不得不經過 catch
    NumberFormatException 來實現。
    正例: if (obj != null) {…}
    反例: try { obj.method(); } catch (NullPointerException e) {…}
  • 【強制】 異常不要用來作流程控制,條件控制。
    說明: 異常設計的初衷是解決程序運行中的各類意外狀況,且異常的處理效率比條件判斷方式
    要低不少。
  • 【強制】 catch 時請分清穩定代碼和非穩定代碼,穩定代碼指的是不管如何不會出錯的代碼。
    對於非穩定代碼的 catch 儘量進行區分異常類型,再作對應的異常處理。
    說明: 對大段代碼進行 try-catch,使程序沒法根據不一樣的異常作出正確的應激反應,也不利
    於定位問題,這是一種不負責任的表現。
    正例: 用戶註冊的場景中,若是用戶輸入非法字符, 或用戶名稱已存在, 或用戶輸入密碼過於
    簡單,在程序上做出分門別類的判斷,並提示給用戶。
  • 【強制】 捕獲異常是爲了處理它,不要捕獲了卻什麼都不處理而拋棄之,若是不想處理它,請
    將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化爲用戶能夠理解的
    內容。
  • 【強制】 有 try 塊放到了事務代碼中, catch 異常後,若是須要回滾事務,必定要注意手動回
    滾事務。
  • 【強制】 finally 塊必須對資源對象、流對象進行關閉,有異常也要作 try-catch。
    說明: 若是 JDK7 及以上,可使用 try-with-resources 方式。
  • 【強制】 不要在 finally 塊中使用 return。
    說明: finally 塊中的 return 返回後方法結束執行,不會再執行 try 塊中的 return 語句。
  • 【強制】 捕獲異常與拋異常,必須是徹底匹配,或者捕獲異常是拋異常的父類。
    說明: 若是預期對方拋的是繡球,實際接到的是鉛球,就會產生意外狀況。
  • 【推薦】 方法的返回值能夠爲 null,不強制返回空集合,或者空對象等,必須添加註釋充分
    說明什麼狀況下會返回 null 值。
    說明: 本手冊明確防止 NPE 是調用者的責任。即便被調用方法返回空集合或者空對象,對調用者來講,也並不是高枕無憂,必須考慮到遠程調用失敗、 序列化失敗、 運行時異常等場景返回
    null 的狀況。
  • 【推薦】 防止 NPE,是程序員的基本修養,注意 NPE 產生的場景:
    1)返回類型爲基本數據類型, return 包裝數據類型的對象時,自動拆箱有可能產生 NPE。
    反例: public int f() { return Integer 對象}, 若是爲 null,自動解箱拋 NPE。
    2) 數據庫的查詢結果可能爲 null。
    3) 集合裏的元素即便 isNotEmpty,取出的數據元素也可能爲 null。
    4) 遠程調用返回對象時,一概要求進行空指針判斷,防止 NPE。
    5) 對於 Session 中獲取的數據,建議 NPE 檢查,避免空指針。
    6) 級聯調用 obj.getA().getB().getC(); 一連串調用,易產生 NPE。
    正例: 使用 JDK8 的 Optional 類來防止 NPE 問題。
  • 【推薦】 定義時區分 unchecked / checked 異常,避免直接拋出 new RuntimeException(),
    更不容許拋出 Exception 或者 Throwable,應使用有業務含義的自定義異常。推薦業界已定義
    過的自定義異常,如: DAOException / ServiceException 等。
  • 【參考】 對於公司外的 http/api 開放接口必須使用「錯誤碼」; 而應用內部推薦異常拋出;
    跨應用間 RPC 調用優先考慮使用 Result 方式,封裝 isSuccess()方法、 「錯誤碼」、 「錯誤簡
    短信息」。
    說明: 關於 RPC 方法返回方式使用 Result 方式的理由:
    1) 使用拋異常返回方式,調用方若是沒有捕獲到就會產生運行時錯誤。
    2) 若是不加棧信息,只是 new 自定義異常,加入本身的理解的 error message,對於調用
    端解決問題的幫助不會太多。若是加了棧信息,在頻繁調用出錯的狀況下,數據序列化和傳輸
    的性能損耗也是問題。
  • 【參考】 避免出現重複的代碼(Don’t Repeat Yourself) ,即 DRY 原則。
    說明: 隨意複製和粘貼代碼,必然會致使代碼的重複,在之後須要修改時,須要修改全部的副
    本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
    正例: 一個類中有多個 public 方法,都須要進行數行相同的參數校驗操做,這個時候請抽取:
    private boolean checkParam(DTO dto) {…}

常見面試題

  • Error 和 Exception 區別是什麼?
    Error 類型的錯誤一般爲虛擬機相關錯誤,如系統崩潰,內存不足,堆棧溢出等,編譯器不會對這類錯誤進行檢測,JAVA 應用程序也不該對這類錯誤進行捕獲,一旦這類錯誤發生,一般應用程序會被終止,僅靠應用程序自己沒法恢復;

    Exception 類的錯誤是能夠在應用程序中進行捕獲並處理的,一般遇到這種錯誤,應對其進行處理,使應用程序能夠繼續正常運行。

  • 運行時異常和通常異常區別是什麼?
    編譯器不會對運行時異常進行檢測,沒有 try-catch,方法簽名中也沒有 throws 關鍵字聲明,編譯依然能夠經過。若是出現了 RuntimeException, 那必定是程序員的錯誤。

    通常一場若是沒有 try-catch,且方法簽名中也沒有用 throws 關鍵字聲明可能拋出的異常,則編譯沒法經過。這類異常一般爲應用環境中的錯誤,即外部錯誤,非應用程序自己錯誤,如文件找不到等。

  • NoClassDefFoundError 和 ClassNotFoundException 區別是什麼?
    NoClassDefFoundError 是一個 Error 類型的異常,是由 JVM 引發的,不該該嘗試捕獲這個異常。引發該異常的緣由是 JVM 或 ClassLoader 嘗試加載某類時在內存中找不到該類的定義,該動做發生在運行期間,即編譯時該類存在,可是在運行時卻找不到了,多是變異後被刪除了等緣由致使;

    ClassNotFoundException 是一個受查異常,須要顯式地使用 try-catch 對其進行捕獲和處理,或在方法簽名中用 throws 關鍵字進行聲明。當使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 動態加載類到內存的時候,經過傳入的類路徑參數沒有找到該類,就會拋出該異常;另外一種拋出該異常的可能緣由是某個類已經由一個類加載器加載至內存中,另外一個加載器又嘗試去加載它。

  • JVM 是如何處理異常的?
    在一個方法中若是發生異常,這個方法會建立一個一場對象,並轉交給 JVM,該異常對象包含異常名稱,異常描述以及異常發生時應用程序的狀態。建立異常對象並轉交給 JVM 的過程稱爲拋出異常。可能有一系列的方法調用,最終才進入拋出異常的方法,這一系列方法調用的有序列表叫作調用棧。
    JVM 會順着調用棧去查找看是否有能夠處理異常的代碼,若是有,則調用異常處理代碼。當 JVM 發現能夠處理異常的代碼時,會把發生的異常傳遞給它。若是 JVM 沒有找到能夠處理該異常的代碼塊,​​​​JVM 就會將該異常轉交給默認的異常處理器(默認處理器爲 JVM 的一部分),默認異常處理器打印出異常信息並終止應用程序。

  • throw 和 throws 的區別是什麼?
    throw 關鍵字用來拋出方法或代碼塊中的異常,受查異常和非受查異常均可以被拋出。
    throws 關鍵字用在方法簽名處,用來標識該方法可能拋出的異常列表。一個方法用 throws 標識了可能拋出的異常列表,調用該方法的方法中必須包含可處理異常的代碼,不然也要在方法簽名中用 throws 關鍵字聲明相應的異常。​​

  • 常見的 RuntimeException 有哪些?

  1. ClassCastException(類轉換異常)
  2. IndexOutOfBoundsException(數組越界)
  3. NullPointerException(空指針)
  4. ArrayStoreException(數據存儲異常,操做數組時類型不一致)
  5. 還有IO操做的BufferOverflowException異常

 

  原文:https://www.imooc.com/article/275564#

相關文章
相關標籤/搜索