讀本篇文章之前,如果讓你敘述一下 Exception Error Throwable 的區別,你能回答出來麼?
你的反應是不是像下面一樣呢?
- 你在寫程式碼時會經常 try catch(Exception)
- 在 log 中會看到 OutOfMemoryError
- Throwable 似乎不常見,但也大概聽說過
一、Exception Error Throwable 關係
直接看下圖,展示了三者之間的關係:
Throwable
是Error
和Exception
的父類。Exception
是程式正常執行中可預料的正常情況,應該被捕獲並進行處理。- 又分為可檢查(checked)和不檢查(unchecked)異常。
- 可檢查異常是在編譯期檢查的一部分,必須顯示捕獲處理。如有的方法 throw exception,那麼呼叫該函式則必須 catch 處理或者再次 throw 出去交給下一層處理。
- 不檢查異常一般指執行時異常(RuntimeException),類似
ArrayIndexOutOfBoundsException
、ArithmeticException
等。一般可由程式碼邏輯避免,可看情況是否捕獲。
Error
一般是正常情況下不太可能出現的,絕大部分 Error 會導致程式處於不可恢復的狀態,所以也不必捕獲。如OutOfMemoryError
。
二、對比一個 Error 和 Exception
你在面試中也許會被問到:
NoClassDefFoundError 和 ClassNotFoundException 有什麼區別?
首先,我們看這倆名字,一個是 Error
另一個是 Exception
,從上面的介紹以及看下面的繼承圖可以得到:ClassNotFoundException
應是編碼時要被捕獲的異常,NoClassDefFoundError
是編譯透過了,但執行時產生的重大問題。
進一步的:
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);
}
}
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 XXError
或 catch Throwable
呢?
- Exception 才是你應該關注處理的異常,這種異常處理後還可以使程式正常執行。
- Error 屬於重大問題,是會使程式直接崩潰的,你捕獲了也沒什麼用,很難讓程式再「活」過來。
- 至於 Throwable,首先不應該捕獲這麼寬泛的問題(比捕獲 Exception 還嚴重),第二其中包含了 Error 也不是你應該處理的問題。
- 因此,Error 和 Throwable 除非你明確知道你在幹什麼,否則不要捕獲這兩種。
四、小結
本篇介紹了 Exception Error Throwable 的區別,並給出了相關例子幫助理解。
回到開頭的問題:「敘述一下 Exception Error Throwable 的區別」你心裡有數了麼?