Switch 2021年日本銷量530萬臺 PS5銷量是Xbox 10倍
Java的異常是類class
,它的繼承關係如下:
┌───────────┐ │ Object │ └───────────┘ ▲ │ ┌───────────┐ │ Throwable │ └───────────┘ ▲ ┌─────────┴─────────┐ │ │ ┌───────────┐ ┌───────────┐ │ Error │ │ Exception │ └───────────┘ └───────────┘ ▲ ▲ ┌───────┘ ┌────┴──────────┐ │ │ │ ┌─────────────────┐ ┌─────────────────┐┌───────────┐ │OutOfMemoryError │... │RuntimeException ││IOException│... └─────────────────┘ └─────────────────┘└───────────┘ ▲ ┌───────────┴─────────────┐ │ │ ┌─────────────────────┐ ┌─────────────────────────┐ │NullPointerException │ │IllegalArgumentException │... └─────────────────────┘ └─────────────────────────┘
從繼承關係可知:Throwable
是異常體系的根,它繼承自Object
。Throwable
有兩個體系:Error
和Exception
,Error
表示嚴重的錯誤,程式對此一般無能為力,例如:
OutOfMemoryError
:記憶體耗盡NoClassDefFoundError
:無法載入某個ClassStackOverflowError
:棧溢位
而Exception
則是執行時的錯誤,它可以被捕獲並處理。
某些異常是應用程式邏輯處理的一部分,應該捕獲並處理。例如:
NumberFormatException
:數值型別的格式錯誤FileNotFoundException
:未找到檔案SocketException
還有一些異常是程式邏輯編寫不對造成的,應該修復程式本身。例如:
NullPointerException
:對某個null
的物件呼叫方法或欄位IndexOutOfBoundsException
:陣列索引越界
Exception
又分為兩大類:
RuntimeException
以及它的子類;- 非
RuntimeException
(包括IOException
、ReflectiveOperationException
等等)
Java規定:
- 必須捕獲的異常,包括
Exception
及其子類,但不包括RuntimeException
及其子類,這種型別的異常稱為Checked Exception。 - 不需要捕獲的異常,包括
Error
及其子類,RuntimeException
及其子類。
注意:編譯器對RuntimeException及其子類不做強制捕獲要求,不是指應用程式本身不應該捕獲並處理RuntimeException。是否需要捕獲,具體問題具體分析。
異常的傳播
當某個方法丟擲了異常時,如果當前方法沒有捕獲異常,異常就會被拋到上層呼叫方法,直到遇到某個try ... catch
被捕獲為止:
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
process2();
}
static void process2() {
Integer.parseInt(null); // 會丟擲NumberFormatException
}
}
通過printStackTrace()
可以打印出方法的呼叫棧,類似:
java.lang.NumberFormatException: null
at java.base/java.lang.Integer.parseInt(Integer.java:614)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at Main.process2(Main.java:16)
at Main.process1(Main.java:12)
at Main.main(Main.java:5)
printStackTrace()
對於除錯錯誤非常有用,上述資訊表示:NumberFormatException
是在java.lang.Integer.parseInt
方法中被丟擲的,從下往上看,呼叫層次依次是:
main()
呼叫process1()
;process1()
呼叫process2()
;process2()
呼叫Integer.parseInt(String)
;Integer.parseInt(String)
呼叫Integer.parseInt(String, int)
。
轉換異常
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}
static void process2() {
throw new NullPointerException();
}
}
打印出的異常棧類似:
java.lang.IllegalArgumentException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
這說明新的異常丟失了原始異常資訊,我們已經看不到原始異常NullPointerException
的資訊了。
為了能追蹤到完整的異常棧,在構造異常的時候,把原始的Exception
例項傳進去,新的Exception
就可以持有原始Exception
資訊。對上述程式碼改進如下:
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
}
static void process2() {
throw new NullPointerException();
}
}
執行上述程式碼,打印出的異常棧類似:
java.lang.IllegalArgumentException: java.lang.NullPointerException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
Caused by: java.lang.NullPointerException
at Main.process2(Main.java:20)
at Main.process1(Main.java:13)
注意到Caused by: Xxx
,說明捕獲的IllegalArgumentException
並不是造成問題的根源,根源在於NullPointerException
,是在Main.process2()
方法丟擲的。
在程式碼中獲取原始異常可以使用Throwable.getCause()
方法。如果返回null
,說明已經是“根異常”了。
有了完整的異常棧的資訊,我們才能快速定位並修復程式碼的問題。
捕獲到異常並再次丟擲時,一定要留住原始異常,否則很難定位第一案發現場!
時來天地皆同力,運去英雄不自由。