关键在于明确“谁该为这个错误负责”:受检异常继承exception而非runtimeexception,编译强制处理,代表外部不确定性;运行时异常继承runtimeexception,编译不检查,暴露内部逻辑缺陷。

区分受检异常和运行时异常,关键不在语法细节,而在于明确“谁该为这个错误负责”。选对异常类型,本质是设计清晰的协作契约。
看继承关系,不看名字也不看怎么抛
这是最准确、最不会被误导的方式:
- 如果异常类直接或间接继承 RuntimeException(比如
NullPointerException、IllegalArgumentException、ArrayIndexOutOfBoundsException),就是运行时异常;编译器不管,运行时才可能崩 - 如果异常类继承 Exception,但不是
RuntimeException的后代(比如IOException、SQLException、ClassNotFoundException),就是受检异常;不处理就编译不过 - IDE 中按住 Ctrl(Windows)或 Cmd(macOS)点击异常类名,一眼就能看到它 extends 谁
看问题来源:外部不确定性 vs 内部逻辑缺陷
决定用哪一类,先问一句:这错是环境导致的,还是代码写错了?
- 受检异常适合外部不确定性:文件突然被删、网络抖动断连、第三方支付回调验签失败。这些不是 bug,而是现实的一部分,调用方理应有策略(重试、降级、提示用户)
-
运行时异常指向内部缺陷:传了 null 收货地址、年龄传 -5、没判空就调
toString()。这类问题不该靠 catch 补救,而应在 API 入口校验、单元测试、Code Review 阶段拦截 - 例子:用户上传文件 → 可能不存在 →
IOException(受检);用户提交订单时收货地址为 null →IllegalArgumentException(运行时)
看分层职责:底层封装,上层表达业务语义
真实项目里,原始异常不能裸奔,要按职责做转换:
-
DAO 或 Client 层:捕获
SQLException或IOException,转成带上下文的运行时异常,如DataAccessException("扣减库存失败", e) -
Service 层:聚焦业务规则,抛自定义运行时异常,名称直指业务含义,如
InsufficientStockException、ActivityAlreadyEndedException -
Controller 层:统一用
@ExceptionHandler拦所有运行时异常,转成标准 JSON 响应(含 code、message、traceId),不暴露堆栈
看调用方角色:内部模块 vs 外部 SDK
同一项目内模块调用,优先用运行时异常;对外提供 SDK 时,可谨慎保留受检异常:
- 内部服务间调用:用运行时异常更轻量,避免层层
throws导致 API 膨胀 - 开放给第三方的 SDK:某些强契约场景(如金融配额超限、证书过期)用受检异常,强制对方意识到必须处理,防止静默忽略
- 遗留系统桥接层:需严格遵循老协议语义时,也可保留受检异常以保证兼容性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











