1. 程式人生 > 遊戲 >Switch 2021年日本銷量530萬臺 PS5銷量是Xbox 10倍

Switch 2021年日本銷量530萬臺 PS5銷量是Xbox 10倍

Java的異常是類class,它的繼承關係如下:

                     ┌───────────┐
                     │  Object   │
                     └───────────┘
                           ▲
                           │
                     ┌───────────┐
                     │ Throwable │
                     └───────────┘
                           ▲
                 ┌─────────┴─────────┐
                 │                   │
           ┌───────────┐       ┌───────────┐
           │   Error   │       │ Exception │
           └───────────┘       └───────────┘
                 ▲                   ▲
         ┌───────┘              ┌────┴──────────┐
         │                      │               │
┌─────────────────┐    ┌─────────────────┐┌───────────┐
│OutOfMemoryError │... │RuntimeException ││IOException│...
└─────────────────┘    └─────────────────┘└───────────┘
                                ▲
                    ┌───────────┴─────────────┐
                    │                         │
     ┌─────────────────────┐ ┌─────────────────────────┐
     │NullPointerException │ │IllegalArgumentException │...
     └─────────────────────┘ └─────────────────────────┘

從繼承關係可知:Throwable是異常體系的根,它繼承自ObjectThrowable有兩個體系:ErrorExceptionError表示嚴重的錯誤,程式對此一般無能為力,例如:

  • OutOfMemoryError:記憶體耗盡
  • NoClassDefFoundError:無法載入某個Class
  • StackOverflowError:棧溢位

Exception則是執行時的錯誤,它可以被捕獲並處理。

某些異常是應用程式邏輯處理的一部分,應該捕獲並處理。例如:

  • NumberFormatException:數值型別的格式錯誤
  • FileNotFoundException:未找到檔案
  • SocketException
    :讀取網路失敗

還有一些異常是程式邏輯編寫不對造成的,應該修復程式本身。例如:

  • NullPointerException:對某個null的物件呼叫方法或欄位
  • IndexOutOfBoundsException:陣列索引越界

Exception又分為兩大類:

  1. RuntimeException以及它的子類;
  2. RuntimeException(包括IOExceptionReflectiveOperationException等等)

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方法中被丟擲的,從下往上看,呼叫層次依次是:

  1. main()呼叫process1()
  2. process1()呼叫process2()
  3. process2()呼叫Integer.parseInt(String)
  4. 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,說明已經是“根異常”了。

有了完整的異常棧的資訊,我們才能快速定位並修復程式碼的問題。

捕獲到異常並再次丟擲時,一定要留住原始異常,否則很難定位第一案發現場!

時來天地皆同力,運去英雄不自由。