应继承Exception以强制调用方处理,适用于可预期需恢复的业务错误;继承RuntimeException用于程序bug等不可恢复错误,不强制处理。

继承 Exception 还是 RuntimeException?关键看是否强制处理
Java 自定义异常的核心判断点在于:你希望调用方**必须显式处理**这个异常,还是允许它“悄悄传播”。Exception 及其子类是检查型异常(checked),编译器强制要求 try-catch 或 throws;而 RuntimeException 是非检查型(unchecked),不强制处理。
- 业务逻辑中可预期、需主动恢复的错误(如余额不足、参数格式非法),适合继承
Exception - 程序内部 bug 或不可恢复的错误(如空指针、数组越界),应继承
RuntimeException,避免污染正常流程 - 不要为了“统一”而全用
RuntimeException——这会让调用方失去对关键错误的感知能力
构造方法怎么写?至少保留三个标准签名
自定义异常类应覆盖父类最常用的构造方法,否则使用者会遇到 new MyException("msg") 编译失败这类低级问题。标准做法是提供以下三个构造函数:
public class InsufficientBalanceException extends Exception {
public InsufficientBalanceException() {
super();
}
public InsufficientBalanceException(String message) {
super(message);
}
public InsufficientBalanceException(String message, Throwable cause) {
super(message, cause);
}}
- 无参构造用于日志或框架自动抛出场景
-
String构造是日常使用频率最高的,务必支持 - 带
Throwable的构造用于链式异常封装(比如捕获SQLException后包装成业务异常)
要不要加字段和方法?只在真有必要时才扩展
多数情况下,仅靠继承 + 标准构造就足够了。只有当异常需要携带额外上下文且被上层明确消费时,才考虑加字段。例如:
立即学习“Java免费学习笔记(深入)”;
- 需要返回错误码供前端展示 → 加
private final int errorCode - 需要记录触发时的用户 ID 用于审计 → 加
private final String userId - 不要加 getter/setter 以外的业务方法 —— 异常不是工具类
- 字段必须
final,保证异常对象不可变
常见误用:给异常加 log() 方法或 sendToMonitor() —— 这混淆了“描述错误”和“处理错误”的职责。
抛出和捕获时要注意什么?别让异常信息丢失
自定义异常的价值在于精准传达问题,但实际中常因用法不当而失效:
- 抛出时不要只写
throw new MyException("error"),优先用带上下文的字符串:throw new MyException("转账失败:目标账户 " + accountId + " 不存在") - 捕获后不要吞掉异常:
catch (MyException e) { /* 空 */ }—— 至少要logger.error("", e) - 跨模块传递时,避免在 catch 块里 new 一个新异常却不传原始 cause:
throw new ServiceException("调用下游失败", e)才能保留堆栈 - Spring 等框架可能对异常类型有特殊处理(如
@ResponseStatus),确保你的异常类满足其扫描条件(通常是 public、有默认构造)
真正难的不是写一个异常类,而是整个团队对每种异常的语义、处理边界和日志规范达成一致。没约定好“这个异常谁负责兜底”,再多的自定义也没用。










