java自定义异常要真正支持异常链,关键不是“能传cause”,而是覆盖无参、带message、带message和cause三种构造方法,确保原始异常堆栈完整保留;继承exception为受检异常,需强制处理,继承runtimeexception为非受检异常,不强制处理;业务字段应private final且不影响cause传递。

Java自定义异常要真正支持异常链,关键不是“能传cause”,而是让每种使用场景都有对应构造方法,并且不破坏原始异常的堆栈和上下文。规范写法的核心是覆盖三种标准构造签名,同时保持Throwable父类语义一致。
必须实现的三个构造方法
只写一个带message的构造器远远不够。调用方可能在不同阶段需要不同信息,缺一不可:
-
无参构造器:用于简单抛出,比如
throw new OrderException();,底层仍能正常打印堆栈 -
带String message的构造器:最常用,明确传递业务提示,如
throw new OrderException("库存不足"); -
带message和Throwable cause的构造器:这是异常链的入口,必须直接调用
super(message, cause),确保原始异常的stack trace被完整保留
继承类型决定是否强制处理
选择父类直接影响调用方义务:
- 继承
Exception→ 受检异常 → 调用处必须try-catch或throws,适合业务必须响应的错误(如支付失败、权限拒绝) - 继承
RuntimeException→ 非受检异常 → 不强制处理,适合程序逻辑错误或不可恢复状态(如参数校验失败、配置缺失)
注意:不要混用。若继承Exception却漏掉throws声明,编译直接报错;若继承RuntimeException却期望调用方强制捕获,实际无法约束。
扩展业务属性要独立封装,不干扰异常链
错误码、订单号、用户ID等业务字段可以加,但不能影响cause传递逻辑:
- 字段声明为
private final,避免被意外修改 - 所有构造器中,只要含
cause参数,就只做super(message, cause)调用,业务字段通过this.xxx = xxx单独赋值 - 提供
getErrorCode()等getter,不提供setter——异常一旦抛出,状态应不可变
示例片段:
public class PaymentException extends Exception {
private final int code;
public PaymentException(int code, String message) {
super(message);
this.code = code;
}
public PaymentException(int code, String message, Throwable cause) {
super(message, cause); // ← 关键:原样传给父类
this.code = code;
}
public int getCode() { return code; }
}
抛出时务必显式传入cause,避免“吞异常”
捕获后重新抛出自定义异常,如果忽略cause,等于切断调试线索:
- ❌ 错误写法:
throw new PaymentException(500, "支付超时");—— 原始网络异常丢失 - ✅ 正确写法:
throw new PaymentException(500, "支付超时", e);—— 堆栈里能看到SocketTimeoutException根源
IDE通常会警告“Swallowed exception”,这类提示不应忽略。异常链的价值,就在层层向上时仍能定位最初故障点。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











