Java编译器仅对Exception及其子类(非RuntimeException分支)做强制检查,未处理则编译失败;而RuntimeException及其子类在编译期不检查,属运行时异常。

编译器报错就是受检异常的明确信号
Java 编译器对 Exception 及其子类做静态检查,但会跳过 RuntimeException 分支。只要你在方法里调用了可能抛出 IOException、SQLException、ClassNotFoundException 的代码,又没加 try-catch 或声明 throws,javac 就会立刻报错:unreported exception XXX; must be caught or declared to be thrown。这不是警告,是编译失败。
而写 String s = null; s.length();,编译器完全不拦——它只管语法和类型,不管运行时会不会空指针。这类代码能生成 class 文件,说明它通过了编译期校验。
- 用 IDE(IntelliJ/VS Code + Java 插件)按住
Ctrl(Windows)或Cmd(macOS)点击异常类名,直接看父类:如果是RuntimeException或其子类,就是运行时异常;否则大概率是受检异常 -
ArithmeticException看名字像“算术错误”,但它继承自RuntimeException,所以不强制处理;UnsupportedEncodingException听起来很“运行时”,但它其实是IOException子类 → 受检异常 - 自定义异常时,继承
Exception但不继承RuntimeException→ 受检;继承RuntimeException→ 运行时
为什么编译器只盯受检异常,不管运行时异常
设计意图不同:FileNotFoundException 发生与否取决于文件系统状态,程序无法在编译时确认“这个路径一定存在”;而 NullPointerException 几乎总是因为 if (obj == null) 漏判、参数未校验等逻辑疏漏——编译器认为这该由开发者在编码阶段预防,而不是靠 catch 吞掉。
强制处理受检异常,本质是把「外部不确定性」显性化:你得决定是重试、降级、提示用户,还是向上推诿(throws)。但层层 throws 到 public API 是反模式,容易让调用方被迫处理本不该关心的底层细节。
- 受检异常适合封装可恢复的外部交互失败(如网络请求超时、DB 连接断开)
- 运行时异常适合暴露不可恢复的编程错误(如传入负数给要求正整数的构造函数)
- 捕获
RuntimeException本身要非常谨慎——除非你在写通用框架做兜底日志或清理资源,否则大概率是在掩盖 bug
常见误判场景:名字、new 和 throws 都不可信
别被异常类名带偏:IllegalStateException 听起来“不合法”,但它属于 RuntimeException;TimeoutException 听起来“超时”,但它在 java.util.concurrent 包下,继承自 Exception → 受检异常(注意:不是 java.lang 下那个同名类)。
new RuntimeException("xxx") 创建的对象仍是运行时异常,编译器照样无视;反过来,throw new IOException() 即使是手动 throw,也必须处理,否则编译失败。
- 不要依赖 “是否写了
throws” 来判断——方法签名里声明throws Exception不代表它抛的是受检异常(Exception包含所有异常) - 不要依赖 “是否在 try 块里” 来判断——
try { ... } catch (Exception e) { ... }会同时捕获两类异常,但编译器不会因此放过未声明的受检异常 - 最稳的方式永远是查继承链:打开 JDK 源码或 Javadoc,确认目标异常类的直接父类是不是
RuntimeException
实际开发中容易忽略的关键点
受检异常的强制处理机制,真正难的不是语法,而是决策:这个异常我该吞掉、转成运行时异常、还是暴露给上层?比如 DAO 层抛 SQLException,Service 层直接 throws 就等于把数据库细节泄露出去;更合理的做法是捕获后包装成业务异常(如 OrderNotFoundException),且让它继承 RuntimeException —— 这样既保留语义,又不污染调用链。
另一个常被忽略的是资源管理:用 try-with-resources 处理 IOException 时,如果 close() 也抛异常,它会被抑制(suppressed),但原始异常仍主导流程。这时候若没检查 getSuppressed(),可能错过关键线索。
说到底,编译器只管“有没有处理”,不管“处理得对不对”。一个被 catch 住却只打日志不反馈、不恢复的受检异常,和一个被放任不管的运行时异常,对系统稳定性来说,危害程度差不多。

















