【Java面試指北】Exception Error Throwable 你分得清麼?

大資料王小皮 發表於 2022-12-04
Java 面試

讀本篇文章之前,如果讓你敘述一下 Exception Error Throwable 的區別,你能回答出來麼?
你的反應是不是像下面一樣呢?

  • 你在寫程式碼時會經常 try catch(Exception)
  • 在 log 中會看到 OutOfMemoryError
  • Throwable 似乎不常見,但也大概聽說過

一、Exception Error Throwable 關係

直接看下圖,展示了三者之間的關係:

  • ThrowableErrorException 的父類。
  • Exception是程式正常執行中可預料的正常情況,應該被捕獲並進行處理。
    • 又分為可檢查(checked)不檢查(unchecked)異常。
    • 可檢查異常是在編譯期檢查的一部分,必須顯示捕獲處理。如有的方法 throw exception,那麼呼叫該函式則必須 catch 處理或者再次 throw 出去交給下一層處理。
    • 不檢查異常一般指執行時異常(RuntimeException),類似 ArrayIndexOutOfBoundsExceptionArithmeticException等。一般可由程式碼邏輯避免,可看情況是否捕獲。
  • Error 一般是正常情況下不太可能出現的,絕大部分 Error 會導致程式處於不可恢復的狀態,所以也不必捕獲。如 OutOfMemoryError

image.png

二、對比一個 Error 和 Exception

你在面試中也許會被問到:

NoClassDefFoundError 和 ClassNotFoundException 有什麼區別?

首先,我們看這倆名字,一個是 Error 另一個是 Exception,從上面的介紹以及看下面的繼承圖可以得到:ClassNotFoundException 應是編碼時要被捕獲的異常,NoClassDefFoundError 是編譯透過了,但執行時產生的重大問題。
image.png

進一步的:
ClassNotFoundException 是執行中動態載入類時出現的問題。
舉例來說,使用 Class.forName 來動態載入一個類,如果你不顯示的 catch 處理,ide 都會給你提示,並且也過不了編譯。

// 錯誤寫法
public void except() {
    Class.forName("com.test.aaa");
}

// 正確寫法
public void except() {
    try {
        Class.forName("com.test.aaa");
    } catch (ClassNotFoundException e) {
        // throw or log
        throw new RuntimeException(e);
        // log.error("ClassNotFoundException: ", e);
    }
}

image.png

NoClassDefFoundError 是編譯時沒問題,但執行時 new 例項找不到。
比如在一個類中引用另一個類的函式,編譯後把另一個類的 class 檔案刪掉:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
        MyPrint.printName();
    }
}
public class MyPrint {
    public static void  printName() {
        System.out.println("my name is zhangsan");
    }
}

使用 javac 編譯,再刪除 MyPrint.class

$ tree com
com
└── shuofxz
    ├── Main.class
    ├── Main.java
    ├── MyPrint.class  # 把這個檔案刪掉
    └── MyPrint.java

執行程式,就會看到 NoClassDefFoundError,並且是由 ClassNotFoundException 引起的。

Exception in thread "main" java.lang.NoClassDefFoundError: com/shuofxz/MyPrint
	at com.shuofxz.Main.main(Main.java:6)
Caused by: java.lang.ClassNotFoundException: com.shuofxz.MyPrint
	at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:419)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	... 1 more

三、捕獲異常程式碼常見問題

一)看下面的程式碼有什麼問題?

try {
  // 業務程式碼
  // ...
  Thread.sleep(1000L);
} catch (Exception e) {
}
  • 捕獲了過於通用的異常 Exception,應改為對應的 InterruptedException。這麼做的目的是因為:第一方便閱讀程式碼,知道可能會出現什麼具體的異常;第二不捕獲意料之外的異常。
  • 不要捕獲異常之後啥都不做(生吞異常)。這就是給自己挖坑,之後程式遇到問題,你很難定位到這裡。

二)再看下面這個,增加了異常列印邏輯,還有什麼問題不?

try {
    // 業務程式碼
    // …
} catch (IOException e) {
    e.printStackTrace();
}
  • 自娛自樂是 ok 的,但不要放到生產環境中。因為 e.printStackTrace() 的功能是:Prints this throwable and its backtrace to the standard error stream。你很難判斷它到底輸出到哪裡去了。
  • 應該用成熟的日誌工具如 Slf4j 等。

三)再來看下面的:

try {
    // 業務邏輯 A
    // 業務邏輯 B
    // 業務邏輯 C
    // ...
} catch (Exception e) {
    log.error("have exception", e);
}
  • 不能因為怕丟失異常捕獲,就把一大段程式碼都框到一個 try-catch 模組中。
  • try-catch 程式碼段會產生額外的效能開銷,它往往會影響 JVM 對程式碼進行最佳化。

四)我們前面介紹了Exception、Error、Throwable,為什麼程式碼中經常能看到 catch XXException,卻幾乎看不到 catch XXErrorcatch Throwable 呢?

  • Exception 才是你應該關注處理的異常,這種異常處理後還可以使程式正常執行。
  • Error 屬於重大問題,是會使程式直接崩潰的,你捕獲了也沒什麼用,很難讓程式再「活」過來。
  • 至於 Throwable,首先不應該不過這麼寬泛的問題(比捕獲 Exception 還嚴重),第二其中包含了 Error 也不是你應該處理的問題。
  • 因此,Error 和 Throwable 除非你明確知道你在幹什麼,否則不要捕獲這兩種。

三、小結

本篇介紹了 Exception Error Throwable 的區別,並給出了相關例子幫助理解。
回到開頭的問題:「敘述一下 Exception Error Throwable 的區別」你心裡有數了麼?