深刻了解 Java 中的異常處理

深刻了解 Java 中的異常處理 + 面試題

在程序開發中,異常處理也是咱們常用到的模塊,只是日常不多去深究異常模塊的一些知識點。好比,try-catch 處理要遵循的原則是什麼,finally 爲何老是能執行,try-catch 爲何比較消耗程序的執行性能等問題,咱們本講內容都會給出相應的答案,固然還有面試中常常被問到的異常模塊的一些面試題,也是咱們本篇要講解的重點內容。java

異常處理基礎介紹

先來看看異常處理的語法格式面試

try{ ... } catch(Exception e){ ... } finally{ ... }

其中,數據庫

  • try:是用來監測可能會出現異常的代碼段。
  • catch:是用來捕獲 try 代碼塊中某些代碼引起的異常,若是 try 裏面沒有異常發生,那麼 catch 也必定不會執行。在 Java 語言中,try 後面能夠有多個 catch 代碼塊,用來捕獲不一樣類型的異常,須要注意的是前面的 catch 捕獲異常類型必定不能包含後面的異常類型,這樣的話,編譯器會報錯。
  • finally:不論 try-catch 如何執行,finally 必定是最後執行的代碼塊,全部一般用來處理一些資源的釋放,好比關閉數據庫鏈接、關閉打開的系統資源等。

異常處理的基本使用,具體能夠參考下面的代碼段:json

try {
    int i = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println(e);
} finally {
    System.out.println("finally");
}

多 catch 的使用,具體能夠參考下面的代碼段:數組

try {
    int i = Integer.parseInt(null);
} catch (ArithmeticException ae) {
    System.out.println("ArithmeticException");
} catch (NullPointerException ne) {
    System.out.println("NullPointerException");
} catch (Exception e) {
    System.out.println("Exception");
}

須要注意的是 Java 虛擬機會從上往下匹配錯誤類型,所以前面的 catch 異常類型不能包含後面的異常類型。好比上面的代碼若是把 Exception 放在最前面編譯器就會報錯,具體能夠參考下面的圖片。性能

異常處理的發展

隨着 Java 語言的發展,JDK 7 的時候引入了一些更加便利的特性,用來更方便的處理異常信息,如 try-with-resources 和 multiple catch,具體能夠參考下面的代碼段:優化

try (FileReader fileReader = new FileReader("");
     FileWriter fileWriter = new FileWriter("")) { // try-with-resources
    System.out.println("try");
} catch (IOException | NullPointerException e) { // multiple catch
    System.out.println(e);
}

異常處理的基本原則

先來看下面這段代碼,有沒有發現一些問題?spa

try {
  // ...
  int i = Integer.parseInt(null);
} catch (Exception e) {
}

以上的這段代碼,看似「正常」,卻違背了異常處理的兩個基本原則:線程

  • 第一,儘可能不要捕獲通用異常,也就是像 Exception 這樣的異常,而是應該捕獲特定異常,這樣更有助於你發現問題;
  • 第二,不要忽略異常,像上面的這段代碼只是加了 catch,但沒有進行如何的錯誤處理,信息就已經輸出了,這樣在程序出現問題的時候,根本找不到問題出現的緣由,所以要切記不要直接忽略異常。

異常處理對程序性能的影響

異常處理當然好用,但必定不要濫用,好比下面的代碼片斷:指針

// 使用 com.alibaba.fastjson
JSONArray array = new JSONArray();
String jsonStr = "{'name':'laowang'}";
try {
    array = JSONArray.parseArray(jsonStr);
} catch (Exception e) {
    array.add(JSONObject.parse(jsonStr));
}
System.out.println(array.size());

這段代碼是藉助了 try-catch 去處理程序的業務邏輯,一般是不可取的,緣由包括下列兩個方面。

  • try-catch 代碼段會產生額外的性能開銷,或者換個角度說,它每每會影響 JVM 對代碼進行優化,所以建議僅捕獲有必要的代碼段,儘可能不要一個大的 try 包住整段的代碼;與此同時,利用異常控制代碼流程,也不是一個好主意,遠比咱們一般意義上的條件語句(if/else、switch)要低效。
  • Java 每實例化一個 Exception,都會對當時的棧進行快照,這是一個相對比較重的操做。若是發生的很是頻繁,這個開銷可就不能被忽略了。

以上使用 try-catch 處理業務的代碼,能夠修改成下列代碼:

// 使用 com.alibaba.fastjson
JSONArray array = new JSONArray();
String jsonStr = "{'name':'laowang'}";
if (null != jsonStr && !jsonStr.equals("")) {
    String firstChar = jsonStr.substring(0, 1);
    if (firstChar.equals("{")) {
        array.add(JSONObject.parse(jsonStr));
    } else if (firstChar.equals("[")) {
        array = JSONArray.parseArray(jsonStr);
    }
}
System.out.println(array.size());

相關面試題

1. try 能夠單獨使用嗎?

答:try 不能單獨使用,不然就失去了 try 的意義和價值。

2. 如下 try-catch 能夠正常運行嗎?

try {
    int i = 10 / 0;
} catch {
    System.out.println("last");
}

答:不能正常運行,catch 後必須包含異常信息,如 catch (Exception e)。

3. 如下 try-finally 能夠正常運行嗎?

try {
    int i = 10 / 0;
} finally {
    System.out.println("last");
}

答:能夠正常運行。

4. 如下代碼 catch 裏也發生了異常,程序會怎麼執行?

try {
    int i = 10 / 0;
    System.out.println("try");
} catch (Exception e) {
    int j = 2 / 0;
    System.out.println("catch");
} finally {
    System.out.println("finally");
}
System.out.println("main");

答:程序會打印出 finally 以後拋出異常並終止運行。

5. 如下代碼 finally 裏也發生了異常,程序會怎麼運行?

try {
    System.out.println("try");
} catch (Exception e) {
    System.out.println("catch");
} finally {
    int k = 3 / 0;
    System.out.println("finally");
}
System.out.println("main");

答:程序在輸出 try 以後拋出異常並終止運行,不會再執行 finally 異常以後的代碼。

6. 常見的運行時異常都有哪些?

答:常見的運行時異常以下:

  • java.lang.NullPointerException 空指針異常;出現緣由:調用了未經初始化的對象或者是不存在的對象;
  • java.lang.ClassNotFoundException 指定的類找不到;出現緣由:類的名稱和路徑加載錯誤,一般是程序

試圖經過字符串來加載某個類時引起的異常;

  • java.lang.NumberFormatException 字符串轉換爲數字異常;出現緣由:字符型數據中包含非數字型字符;
  • java.lang.IndexOutOfBoundsException 數組角標越界異常,常見於操做數組對象時發生;
  • java.lang.ClassCastException 數據類型轉換異常;
  • java.lang.NoClassDefFoundException 未找到類定義錯誤;
  • java.lang.NoSuchMethodException 方法不存在異常;
  • java.lang.IllegalArgumentException 方法傳遞參數錯誤。

7. Exception 和 Error 有什麼區別?

答:Exception 和 Error 都屬於 Throwable 的子類,在 Java 中只有 Throwable 及其之類才能被捕獲或拋出,它們的區別以下:

  • Exception(異常)是程序正常運行中,能夠預期的意外狀況,而且可使用 try/catch 進行捕獲處理的。Exception 又分爲運行時異常(Runtime Exception)和受檢查的異常(Checked Exception),運行時異常編譯能經過,但若是運行過程當中出現這類未處理的異常,程序會終止運行;而受檢查的異常,要麼用 try/catch 捕獲,要麼用 throws 字句聲明拋出,不然編譯不會經過。
  • Error(錯誤)是指突發的非正常狀況,一般是不能夠恢復的,好比 Java 虛擬機內存溢出,諸如此類的問題叫作 Error。

8. throw 和 throws 的區別是什麼?

答:它們的區別以下:

  • throw 語句用在方法體內,表示拋出異常由方法體內的語句處理,執行 throw 必定是拋出了某種異常;
  • throws 語句用在方法聲明的後面,該方法的調用者要對異常進行處理,throws 表明可能會出現某種異常,並不必定會發生這種異常。

9. Integer.parseInt(null) 和 Double.parseDouble(null) 拋出的異常同樣嗎?爲何?

答:Integer.parseInt(null) 和 Double.parseDouble(null) 拋出的異常類型不同,以下所示:

  • Integer.parseInt(null) 拋出的異常是 NumberFormatException;
  • Double.parseDouble(null) 拋出的異常是 NullPointerException。

至於爲何會產生不一樣的異常,其實沒有特殊的緣由,主要是因爲這兩個功能是不一樣人開發的,於是就產生了兩種不一樣的異常信息。

10. NoClassDefFoundError 和 ClassNoFoundException 有什麼區別?

  • NoClassDefFoundError 是 Error(錯誤)類型,而 ClassNoFoundExcept 是 Exception(異常)類型;
  • ClassNoFoundExcept 是 Java 使用 Class.forName 方法動態加載類,沒有加載到,就會拋出 ClassNoFoundExcept 異常;
  • NoClassDefFoundError 是 Java 虛擬機或者 ClassLoader 嘗試加載類的時候卻找不到類訂閱致使的,也就是說要查找的類在編譯的時候是存在的,運行的時候卻找不到,這個時候就會出現 NoClassDefFoundError 的錯誤。

11. 使用 try-catch 爲何比較耗費性能?

答:這個問題要從 JVM(Java 虛擬機)層面找答案了。首先 Java 虛擬機在構造異常實例的時候須要生成該異常的棧軌跡,這個操做會逐一訪問當前線程的棧幀,而且記錄下各類調試信息,包括棧幀所指向方法的名字,方法所在的類名、文件名,以及在代碼中的第幾行觸發該異常等信息,這就是使用異常捕獲耗時的主要緣由了。

12. 常見的 OOM 緣由有哪些?

答:常見的 OOM 緣由有如下幾個:

  • 數據庫資源沒有關閉;
  • 加載特別大的圖片;
  • 遞歸次數過多,並一直操做未釋放的變量。

13. 如下程序的返回結果是?

public static int getNumber() {
    try {
        int number = 0 / 1;
        return 2;
    } finally {
        return 3;
    }
}

A:0

B:2

C:3

D:1

答:3

題目解析:程序最後必定會執行 finally 裏的代碼,會把以前的結果覆蓋爲 3。

14. finally、finalize 的區別是什麼?

答:finally、finalize 的區別以下:

  • finally 是異常處理語句的一部分,表示老是執行;
  • finalize 是 Object 類的一個方法,子類能夠覆蓋該方法以實現資源清理工做,垃圾回收以前會調用此方法。

15. 爲何 finally 總能被執行?

答:finally 總會被執行,都是編譯器的做用,由於編譯器在編譯 Java 代碼時,會複製 finally 代碼塊的內容,而後分別放在 try-catch 代碼塊全部的正常執行路徑及異常執行路徑的出口中,這樣 finally 纔會無論發生什麼狀況都會執行。


歡迎關注個人公衆號,回覆關鍵字「Java」 ,將會有大禮相送!!! 祝各位面試成功!!!

相關文章
相關標籤/搜索