CodeGym /課程 /JAVA 25 SELF /建立自訂例外

建立自訂例外

JAVA 25 SELF
等級 24 , 課堂 1
開放

1. 簡介

Java 標準程式庫已經提供了許多例外:NullPointerExceptionIllegalArgumentExceptionIOException 等等。但有時標準例外不足以清楚地描述你的程式中發生的錯誤。

實際範例:
你正在撰寫銀行應用。使用者嘗試提領超過帳戶餘額的金額。你可以拋出 IllegalArgumentException。但若改用自訂例外,例如 InsufficientFundsException,語意會清楚得多,讀到程式碼就能立刻明白發生了什麼事。

自訂例外是一種恰到好處的應用程式客製化。它讓你能更細緻地處理問題,並且僅從名稱(前提是命名合理!)就能看出發生了什麼。此外,它們具有自我文件化的特性:方法宣告中的 throws MyException 會直接告訴讀者可能出現哪些錯誤。再者,你也能加入額外欄位(例如餘額、交易金額等)。

2. 如何建立自己的例外?

很簡單:建立一個新類別,繼承自某個標準例外類別。

  • 對於 受檢(checked)例外 — 繼承自 Exception
  • 對於 未受檢(unchecked)— 繼承自 RuntimeException

範例:受檢例外

public class InvalidCredentialsException extends Exception {
    public InvalidCredentialsException(String message) {
        super(message); // 將訊息傳給父類別
    }
}

現在你可以在程式碼中拋出這個例外:

if (!login.equals("admin") || !password.equals("1234")) {
    throw new InvalidCredentialsException("帳號或密碼錯誤");
}

範例:未受檢例外

public class NegativeBalanceException extends RuntimeException {
    public NegativeBalanceException(String message) {
        super(message);
    }
}

何時使用受檢,何時使用未受檢?

  • 受檢 — 當錯誤是可預期且可處理的(例如驗證錯誤、檔案不存在、使用者提供的資料不正確)。
  • 未受檢 — 當錯誤與程式邏輯中的 bug 有關(例如除以零、破壞不變式)。

3. 建構子:如何讓例外資訊更完整

通常,例外類別至少會實作一個帶有參數 String message 的建構子。但也常會加入其他版本:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException() {
        super();
    }
    public ScoreLimitExceededException(String message) {
        super(message);
    }
    public ScoreLimitExceededException(String message, Throwable cause) {
        super(message, cause);
    }
    public ScoreLimitExceededException(Throwable cause) {
        super(cause);
    }
}

說明:

  • message — 錯誤的文字描述。
  • cause — 原因(另一個例外),當你想要「包裹」一個錯誤於另一個錯誤時使用。

建議: 如果不確定需要哪些建構子,至少提供接收字串的那一個。

4. 在程式碼中使用自訂例外

來看個例子:我們有一個使用者,該使用者有分數,且不得將分數加到超過 100

public class User {
    private String name;
    private int score;

    public User(String name) {
        this.name = name;
        this.score = 0;
    }

    public void addScore(int points) throws ScoreLimitExceededException {
        if (score + points > 100) {
            throw new ScoreLimitExceededException("超出分數上限!嘗試加入: " + points);
        }
        this.score += points;
    }
}

例外類別:

public class ScoreLimitExceededException extends Exception {
    public ScoreLimitExceededException(String message) {
        super(message);
    }
}

處理:

try {
    user.addScore(60);
    user.addScore(50); // 這裡會拋出例外!
} catch (ScoreLimitExceededException e) {
    System.out.println("錯誤: " + e.getMessage());
}

結果:

錯誤: 超出分數上限!嘗試加入: 50

你可能會問:如果不用例外,而是用條件 if,例如回傳 false 或其他特殊值來表示操作失敗,行不行?例如:

public boolean addScore(int points) {
    if (score + points > 100) {
        return false; // 或者乾脆丟個 RuntimeException,如果你不想處理
    }
    this.score += points;
    return true;
}

雖然這看起來更簡單,但在面對嚴重錯誤或應用程式邏輯被破壞時,這種做法有缺點。

首先,回傳 false 或其他值來表示錯誤,要求呼叫端 一律檢查 傳回值。若開發者忘了這麼做,錯誤可能被忽略,導致不可預期的行為。相反地,例外會 強制處理(對受檢例外而言),或者至少在未攔截時 清楚地發出問題訊號

其次,例外 更能清楚地傳達錯誤語意。回傳 false 可能代表任何事情:「失敗」、「不適用」、「不可用」。例外 ScoreLimitExceededException 則明白指出:「分數上限已被超過」。這能提升程式的可讀性與可維護性。

第三,例外允許你 集中處理錯誤。與其在所有呼叫 addScore 的地方散落 if 檢查,你可以在單一位置攔截例外並採取相應措施:提示使用者、寫入日誌或回滾交易。

最後,像超出上限這類問題,確實是 例外情況(名稱也由此而來)。正常的執行流程假設分數會成功加入;若不是如此,代表商業邏輯或物件不變式遭到破壞,正是使用例外的理想場景。

5. 在例外中加入自訂欄位

有時把額外資料放進自訂例外很有幫助,有助於後續處理。

範例:

public class ScoreLimitExceededException extends Exception {
    private int currentScore;
    private int attemptedAdd;

    public ScoreLimitExceededException(String message, int currentScore, int attemptedAdd) {
        super(message);
        this.currentScore = currentScore;
        this.attemptedAdd = attemptedAdd;
    }

    public int getCurrentScore() { 
        return currentScore; 
    }
    public int getAttemptedAdd() { 
        return attemptedAdd; 
    }
}

使用:

if (score + points > 100) {
    throw new ScoreLimitExceededException(
        "超出分數上限!",
        this.score,
        points
    );
}

6. 實用細節

如何命名你的例外?

在 Java 中,慣例是以後綴 Exception 命名自訂例外: InvalidUserInputExceptionInsufficientFundsExceptionScoreLimitExceededException

不要把你的例外命名成 ErrorWarning —— 這可能會誤導其他開發者(甚至幾週後也會誤導你自己)。

在哪裡與何時拋出自訂例外?

  • 進行使用者資料驗證時(例如名稱為空、年齡為負數)。
  • 違反商業規則時(例如超出上限、嘗試提領超過帳戶餘額)。
  • 與外部服務互動發生錯誤時(例如服務不可用、逾時)。

7. 建立自訂例外的常見錯誤

錯誤 № 1:繼承自錯誤的類別。
請繼承自 Exception(或 RuntimeException),而不是 ThrowableError

錯誤 № 2:沒有加入帶訊息的建構子。
沒有接受字串(String message)的建構子,你的例外將缺乏訊息,會很難除錯。

錯誤 № 3:用標準例外處理商業邏輯。
在需要自訂且「能說明問題」的例外時,不要丟出 NullPointerExceptionIllegalArgumentException

錯誤 № 4:濫用自訂例外。
不要為每個小問題都建立一個例外類別。若錯誤並非你的領域所特有,請使用標準例外。

錯誤 № 5:缺少序列化(少見但可能)。
如果你的例外需要透過網路傳送或被保存,應實作 implements Serializable。不過對簡單的應用來說,這並非關鍵。

1
任務
JAVA 25 SELF, 等級 24, 課堂 1
上鎖
遊戲門衛:檢查玩家分數
遊戲門衛:檢查玩家分數
1
任務
JAVA 25 SELF, 等級 24, 課堂 1
上鎖
密碼堡壘:用戶資料保護
密碼堡壘:用戶資料保護
1
任務
JAVA 25 SELF, 等級 24, 課堂 1
上鎖
遊樂園年齡檢查:靈活的錯誤訊息
遊樂園年齡檢查:靈活的錯誤訊息
1
任務
JAVA 25 SELF, 等級 24, 課堂 1
上鎖
魔法文物倉庫管理:溢位詳情
魔法文物倉庫管理:溢位詳情
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION