钩子:编译器的双标现场
上一段代码,编译器直接飘红不让你过:
FileInputStream in = new FileInputStream("no_such_file.txt");
// 编译报错:unhandled exception: java.io.FileNotFoundException
而下面这段,编译器一声不吭,炸了算你自己的:
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // 编译通过,运行时 ArrayIndexOutOfBoundsException
同样是异常,为什么一个非要你处理,一个懒得管?
核心:异常家族树
先看族谱(收藏这张图):
Throwable(万物之祖)
├── Error —— JVM 级重大故障(栈溢出等),程序管不了
└── Exception —— 程序能管的都在这
├── RuntimeException(运行时异常)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ └── ArithmeticException ...
└── 其他 Exception(受检异常)
├── IOException / FileNotFoundException
└── SQLException ...
规矩一句话:RuntimeException 及其子孙,编译器不检查(非受检);其他 Exception,编译器强制处理(受检)。Error 不是给你处理的,别碰。

正题:编译器为什么双标?
想清楚一件事就通了:这两类异常的来源不一样。
受检异常来自"外部世界"——文件可能不存在、网络可能断、数据库可能连不上。这不是代码写错了,是客观世界有变数。编译器逼你提前表态:出事了怎么办?
// 姿势 1:try-catch 当场接住(自己能兜底)
try {
FileInputStream in = new FileInputStream("config.txt");
in.close();
} catch (IOException e) {
System.out.println("配置缺失,用默认配置");
}
// 姿势 2:throws 上交(自己兜不了,谁调用谁处理)
static void readFile(String path) throws IOException {
FileInputStream in = new FileInputStream(path); // 不 try,直接上交
in.close();
}
运行时异常来自"代码的锅"——空指针、下标越界、除零,本质是逻辑漏洞。编译器认为:这种事你该把代码改对,而不是到处 try 把 bug 藏起来。
// 反面教材:用 try 掩盖逻辑漏洞
try {
System.out.println(arr[5]);
} catch (ArrayIndexOutOfBoundsException e) {
// 压住不报——bug 还在,只是看不见了
}
// 正解:写代码时就防住(第 12 篇讲过)
if (5 < arr.length) { ... }
决策:该捕获还是抛出?
一个判断就够——这个后果你处理得了吗?
- 处理得了(给默认值、重试一次、提示用户)→ catch;
- 处理不了(缺配置、缺文件,程序没法继续)→ throws 上交,让能处理的人接;
- 运行时异常别 try,改代码才是根治。
顺便说:写 demo 的时候我被编译器逮了一次——in.close() 居然也抛受检 IOException,不处理编译不过。你以后写 IO 代码一定会遇到,先别慌,这是编译器在帮你。(关资源的终极优雅姿势是 try-with-resources,第 30 篇专讲。)
速查表
| 对比项 | 受检异常 | 运行时异常 |
|---|---|---|
| 代表 | IOException、SQLException | NPE、越界、除零 |
| 继承自 | Exception(非 RuntimeException) | RuntimeException 及其子孙 |
| 编译器 | 强制处理(try 或 throws) | 不检查 |
| 来源 | 外部世界的不确定 | 代码逻辑漏洞 |
| 正确姿势 | catch 兜底 / throws 上交 | 改代码防住,别 try 藏 bug |
本文示例代码均已本地实测通过,可直接运行。
对应文件:ExceptionDemo.java
橙码酱,分享更有用的技术知识,助你成为更优秀的开发者。