运行时异常表达“代码错误”,继承runtimeexception,不强制处理,用于参数非法等逻辑错误;受检异常表达“外部可恢复风险”,继承exception,强制处理,用于余额不足等业务场景。

自定义运行时异常和受检异常,关键不在“怎么写”,而在于“想表达什么”——它直接告诉调用方:这事是代码写错了,还是环境出问题了?
运行时异常:用于表达“不该发生,但发生了”的逻辑错误
继承 RuntimeException,编译器不强制处理,目的是让问题暴露得早、查得清。
- 适用场景:参数非法、状态违例、契约破坏——比如传入负数年龄、重复提交已处理订单、调用未初始化的服务实例
- 典型做法:在方法入口做校验,立刻抛出自定义 RuntimeException(如
InvalidAgeException),不依赖 try-catch 拦截 - 好处:调用方无需写一堆无意义的
throws或空 catch;单元测试能直接触发并定位问题;日志堆栈指向真实出错行 - 反例:把
IllegalArgumentException包一层改成受检异常,强迫所有上层代码加try——这不是健壮,是干扰
受检异常:用于表达“可能发生,且调用方应主动应对”的外部风险
继承 Exception(且非 RuntimeException 子类),编译器强制你面对,逼你思考恢复路径。
- 适用场景:业务中明确可预期、可补救的失败,比如支付余额不足、库存扣减失败、用户权限临时失效
- 典型做法:定义
InsufficientBalanceException或InventoryLockTimeoutException,方法签名声明throws,由业务层决定重试、降级或提示用户 - 好处:API 接口自带契约——调用者一眼知道“这个操作可能失败,你得准备对策”;避免静默失败(比如扣款失败却没返回任何提示)
- 注意:不要为“技术性失败”滥用受检异常,比如把
NullPointerException改成受检异常——这掩盖缺陷,不是增强健壮性
选错父类会误导整个调用链
继承关系不是技术细节,而是设计语言:
- 如果异常代表“你传参错了”,就该是 RuntimeException 子类——它提醒开发者改代码,而不是加 catch
- 如果异常代表“这笔钱确实不够付,但你可以换支付方式”,那就该是 Exception 子类——它要求业务层做决策,而不是跳过
- 点开源码看继承链最准:Ctrl/Cmd + 点击异常类名,确认它是直接/间接继承
RuntimeException还是普通Exception
一个常见误用:把所有业务异常都做成受检的
结果往往是层层 throws,最终在 Controller 层统一 catch,变成“形式上处理,实质上吞掉”。真正该做的,是区分:
- 系统级不可控风险(如网络超时、DB 连接断)→ 用受检异常或封装为统一错误响应
- 业务规则冲突(如“不能连续签到七天”)→ 用运行时异常快速失败,配合全局异常处理器转成友好提示
- 用户输入校验失败(如手机号格式错)→ 不抛异常,用 DTO 校验框架(如 @Valid)提前拦截
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











