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);
    }
}

何时使用受检异常,何时使用非受检异常?

  • 受检异常——当错误是可预期且可以被处理时(例如,校验错误、文件缺失、用户数据不正确)。
  • 非受检异常——当错误与程序逻辑中的缺陷相关(例如,除以零、破坏不变式)。

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 中,通常给自定义异常加上后缀 ExceptionInvalidUserInputExceptionInsufficientFundsExceptionScoreLimitExceededException

不要把异常命名为 ErrorWarning —— 这会误导其他开发者(也可能在几周后误导你自己)。

在哪里、何时抛出自定义异常?

  • 在校验用户数据时(例如,姓名为空、年龄为负)。
  • 在违反业务规则时(例如,超出限额、尝试提取超过账户余额的金额)。
  • 在与外部服务交互出错时(例如,服务不可用、超时)。

7. 创建自定义异常时的常见错误

错误 1:继承自错误的类。
应从 Exception(或 RuntimeException)继承,而不是从 ThrowableError 继承。

错误 2:未添加带消息的构造函数。
没有接受字符串(String message)的构造函数,你的异常将没有说明信息,调试会变得困难。

错误 3:用标准异常处理业务逻辑。
在需要自定义“更具表达性”的异常的地方,不要抛出 NullPointerExceptionIllegalArgumentException

错误 4:滥用自定义异常。
不要为每个小问题都创建一个单独的异常类。如果错误并非你的领域特有,请使用标准异常。

错误 5:缺少序列化(不常见,但会发生)。
如果你的异常需要通过网络传输或被持久化,建议实现 implements Serializable。不过对于简单应用并非必需。

评论 (1)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
ncksllpo 级别 35,Cherkasy,Ukraine
11 二月 2026
🤑