java异常体系必须继承throwable,业务异常优先用runtimeexception子类;避免深度继承,按错误响应动作而非流程节点分类;全局处理器用instanceof精准匹配,不依赖字符串判断。

Java异常类继承体系必须从Throwable出发
Java里所有异常都得是Throwable的子类,没得商量。你自定义的异常如果没继承Exception或Error(二者都继承Throwable),编译器直接报错:java.lang.Error: Unresolved compilation problem。
实际开发中,业务异常几乎全走Exception分支,别碰Error——那是JVM崩溃、栈溢出这类程序无法恢复的场景,你拦不住,也不该拦。
-
RuntimeException及其子类:不强制捕获,适合逻辑错误(如NullPointerException)、参数校验失败等“本不该发生但发生了”的情况 - 非
RuntimeException的Exception子类:必须显式try-catch或throws,适合可预期的外部失败(如IOException、数据库连接超时) - 别让自定义异常同时继承
RuntimeException又声明throws——语义矛盾,调用方会困惑到底要不要处理
设计业务异常时,优先用RuntimeException子类 + 构造函数重载
多数业务系统不需要检查型异常(checked exception)。强制上层处理只会催生catch { e.printStackTrace(); }这种摆烂写法,掩盖真实问题。
一个干净的业务异常类,重点在构造函数灵活性,而不是堆继承深度:
立即学习“Java免费学习笔记(深入)”;
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException() {
super("订单未找到");
}
public OrderNotFoundException(String message) {
super(message);
}
public OrderNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public OrderNotFoundException(Long orderId) {
super("订单不存在: " + orderId);
}
}
- 保留无参构造方便测试和快速抛出
- 提供
String和String + Throwable构造函数,兼容日志链路追踪(比如包装SQLException) - 按领域加语义化构造函数(如
OrderNotFoundException(Long orderId)),避免 everywherenew OrderNotFoundException("订单不存在: " + id) - 别给异常类加getter/setter或业务字段——异常不是DTO,携带过多状态反而干扰错误归因
不要为了“层次感”硬套多级继承
常见误区:建BaseBusinessException → OrderException → OrderCreateException → OrderCreateStockNotEnoughException。层级越深,维护成本越高,捕获时反而更难精准处理。
真正影响处理逻辑的是“怎么应对”,不是“它叫什么”。比如库存不足和支付超时,处理方式完全不同,但都属于订单创建失败——那它们就该是平级的、不同名字的RuntimeException子类。
- 按**错误响应动作**分类型,而不是按**业务流程节点**分类型。例如:
InsufficientStockException(需提示用户补货)、PaymentTimeoutException(需允许重试) - 同一父类下最多2–3个直接子类。再多就说明分类维度错了,或者该合并了
- 如果多个异常共享相同HTTP状态码或告警规则,用接口标记(如
interface RecoverableException),别靠继承树硬凑
全局异常处理器里,instanceof比反射取类名更可靠
Spring Boot常用@ControllerAdvice统一返回错误JSON。这时候别用e.getClass().getSimpleName().contains("NotFound")这种字符串匹配——重构改名就挂。
用instanceof判断类型清晰、编译期检查、性能也好:
@ExceptionHandler
public ResponseEntity<ErrorResponse> handle(OrderNotFoundException e) {
return ResponseEntity.status(404).body(new ErrorResponse("ORDER_NOT_FOUND", e.getMessage()));
}
- 每个具体异常类单独写
@ExceptionHandler方法,别塞进一个大if-else里——Spring能自动匹配,代码可读性高,也方便单元测试覆盖 - 如果多个异常要走同一处理逻辑(比如都返回500),提取公共父类并针对该父类写处理器,而不是用
instanceof堆条件 - 注意
@ExceptionHandler方法参数顺序:Spring按声明顺序匹配,把更具体的异常(如OrderNotFoundException)放前面,泛化的(如RuntimeException)放后面,否则前者永远捕不到










