Java编译器仅强制处理受检异常(继承Exception但非RuntimeException的子类),如FileNotFoundException;而RuntimeException及其子类(如NullPointerException)编译时放行,运行时抛出。

看编译器会不会拦你——最准的现场判断法
Java 编译器只对受检异常(Checked Exception)强制要求处理:不 try-catch,也不在方法签名加 throws,就直接编译失败。而 RuntimeException 及其子类,比如 NullPointerException、IllegalArgumentException,编译器完全不管,写完就能过。
-
FileNotFoundException→ 编译报错:“Unhandled exception type FileNotFoundException” → 必须处理 → 受检异常 -
s.toString()(s == null)→ 编译通过,运行时抛NullPointerException→ 运行时异常 - 查不准?按住
Ctrl(Windows)或Cmd(macOS)点进异常类源码,看它是不是直接或间接继承自RuntimeException;如果不是,且是Exception的子类,那就是受检的
别信名字:比如 UnsupportedEncodingException 听起来像“不支持”,但它其实是 IOException 的子类 → 受检异常。
文件/网络/数据库出问题,优先用受检异常
这类异常对应的是程序无法控制但可预期的外部状况,比如磁盘上文件被删了、远程服务暂时不可达、数据库连接池耗尽。它们不是 bug,而是环境现实。
- 用
IOException或SQLException暴露风险,迫使调用方考虑“如果读不到怎么办?”“连不上库要不要重试或降级?” - 不该一 catch 就吞掉:
catch (IOException e) { }是典型掩盖问题,比不处理还糟 - 更合理的做法是:向上声明
throws IOException,让业务层决定恢复策略(如返回默认值、走缓存、提示用户重试)
反例:把 new FileInputStream("config.txt") 包在 try-catch 里静默吞掉,结果配置永远加载失败,日志里却没线索。
参数错、逻辑崩、状态非法,用 RuntimeException
这类异常反映的是程序内部缺陷或非法输入,应该靠校验、测试、设计约束来提前拦截,而不是靠运行时捕获兜底。
NullPointerException:说明你没做if (obj != null)防御,或不该传 null 却传了IllegalArgumentException:比如传了个负数给“年龄”字段,这是契约破坏,该在入口校验ArrayIndexOutOfBoundsException:下标越界,通常意味着循环条件或边界计算有误自定义异常时,如果想表达“调用方违反了约定”,继承
RuntimeException;如果想表达“这事可能真发生,你得应对”,才继承Exception别为了“看起来规范”硬把
IllegalArgumentException改成受检异常——这会让所有调用者被迫写一堆无意义的try
IDE 提示和日志里怎么快速定位类型
实际 debug 时,光看异常名容易误判,尤其当项目里有大量自定义异常。
- 看堆栈第一行:如果以
java.lang.开头(如java.lang.NullPointerException),基本是运行时异常;以java.io.、java.sql.、javax.naming.开头的,大概率是受检异常 - 在 IDEA 或 VS Code 中,把异常类名高亮,按
Ctrl+Click跳转,一眼看到extends RuntimeException还是extends Exception - 日志中如果只看到
Exception而没具体类名,说明有人写了catch (Exception e)—— 这是坏习惯,会把受检和运行时混在一起,掩盖真实问题类型
真正容易被忽略的,是那些“本该在开发阶段暴露,却被层层 catch (Exception e) 吞掉”的运行时异常。它们不会让编译失败,但会让线上行为变得不可预测。

















