5.2 运行时异常与编译时异常
Exception 家谱里的两个岔路口正式收网:RuntimeException 一派(运行时异常)编译器挥手放行,运行时才炸——多半是你程序自己的 bug;另一派(编译时异常)编译器当场拦下,逼你写好预案才准编译通过——全是外部世界不可控的风险。一句话:自己的 bug 改代码,外部风险写预案。
1. 为什么要分两派?
5.1 的家谱里,Exception 底下有两支。本节把这条线讲透——划分标准不是"严重程度",而是"这事该怪谁":
- 运行时异常:怪程序自己。除零、空指针、越界——代码写对了就不会发生。编译器的态度:这是你的 bug,改代码去,我不管;
- 编译时异常:怪外部世界。文件可能被删、网络可能断、硬盘可能满——你代码写得再完美,这些事照样可能发生。编译器的态度:这事你控制不了,但预案必须写,不写不许跑。
编译器就像安检员:运行时异常是你自己揣的雷(它假设你会自己拆);编译时异常是外部来的不明包裹(必须申报处理方案才放行)。
2. 运行时异常(unchecked):编译器放行,运行才炸
定义:RuntimeException 及其所有子类。 又叫"未检异常"(unchecked——编译器不检查)。
老朋友点名(全部亲手炸过):
| 异常类 | 触发场景 | 出身地 |
|---|---|---|
ArithmeticException |
整数除以 0 | 5.1 Demo01 |
NullPointerException |
钥匙是 null 还去用 | 3.2 |
ArrayIndexOutOfBoundsException |
索引超出范围 | 2.7 |
ClassCastException |
转型没验货 | 4.4 |
NumberFormatException |
把"abc"转数字 | 会认即可 |
这派的共同点:
- 编译能过——编译器一个都不拦;
- 多半是逻辑 bug——空指针说明没判 null,越界说明边界没算对;
- 正确的根治方式是改代码(判空、检查索引、instanceof 验货),而不是到处 try-catch 把 bug 接住假装没事。
编译器放行只是不逼你。真到该接的场合(比如用户输入导致的 NumberFormatException——外部输入你控制不了)照样要 try-catch。判断标准还是那句:能靠改代码根除的改代码,控制不了外部因素的写预案。
3. 编译时异常(受检):编译器当场拦下
定义:Exception 里除了 RuntimeException 之外的所有异常。 又叫"受检异常 / 检查性异常"(checked——编译器要检查)。
常见几位(会认即可):
| 异常类 | 场景 |
|---|---|
ClassNotFoundException |
要加载的类找不到 |
FileNotFoundException |
要读的文件不存在 |
IOException |
读写数据时出意外(文件/网络) |
ParseException |
文本按格式解析失败(如日期) |
InterruptedException |
线程睡觉时被打断 |
体验一次"编译器逼你写预案"——在 IDEA05 里新建 Demo02,抄这一行:
public class Demo02 {
public static void main(String[] args) {
Class.forName("java.lang.String"); // 按名字加载类
}
}
运行?根本轮不到运行——编译直接红字:
Unhandled exception: java.lang.ClassNotFoundException
Class.forName 这个方法天生可能遇到"类找不到",而类找不到是编译时异常——编译器逼你二选一(IDEA 会用红波浪线 + Alt+Enter 提示这两个选项):
- try-catch 接住:自己写预案处理——5.3 学;
- throws 往上甩:在方法签名声明"我这处理不了,谁调用谁负责"——5.3 学。
先感受关键结论:同样一行代码,运行时异常编译器看都不看,编译时异常直接拦下——两派的待遇差异,一眼见分晓。
4. 两派完整对照表
| 维度 | 运行时异常 | 编译时异常(受检) |
|---|---|---|
| 家谱位置 | RuntimeException 及其子类 |
Exception 里其余的 |
| 别名 | 未检异常 unchecked | 受检异常 / 检查性异常 checked |
| 编译器态度 | 放行,不强制处理 | 当场拦下,必须 try-catch 或 throws |
| 什么时候暴露 | 运行时才炸 | 编译期就逼你面对 |
| 事出何方 | 程序自己的 bug | 外部条件不可控 |
| 根治方式 | 改代码(判空/查边界/验货) | 写预案(接住或上抛) |
| 典型代表 | 除零、空指针、越界、转型失败 | 文件找不到、类找不到、IO 出错 |
5. 动手:亲眼看编译器拦人
在 IDEA05 的 Demo02 里完成三个实验:
三个 30 秒实验
- 被编译器拦一次:抄第 3 节的
Class.forName行,看红波浪线和Unhandled exception报错——Alt+Enter 看 IDEA 给的两个预案选项,先不点。 - 运行时异常不被拦:在旁边写一行
int r = 10 / 0;——编译器毫无反应(能编译)。两派待遇同屏对比。 - 拆一次雷:把
10 / 0改成int b = 0; int r = args.length > 0 ? 10 / b : 10;之前先想想——不管怎么绕,除零风险还在代码里,编译器照样不管。运行时异常的安全只能靠你自己改逻辑。
6. 常见误区
两码事。编译错误是语法不对(少分号),改语法就好;编译时异常语法完全正确,编译器拦你是因为你没写意外预案——它要的是 try-catch 或 throws,不是改语法。
是"不强制",不是"不该"。该判空的判空、该验货的验货——根治靠改代码;实在控制不了的外部因素(如用户输入),照样要接。
try-catch 只是接住意外后的处理方案,不阻止意外发生。除零不会因为包了 try-catch 就变成能除。
RuntimeException 只是 Exception 的一个分支。判断口诀:是不是 RuntimeException 或它的子类?是 → 运行时异常;不是(但仍属于 Exception)→ 编译时异常。
分派标准是"怪谁",不是"谁重"。空指针(运行时)可能让整个服务挂掉;文件找不到(编译时)换个路径就好。严重程度和处理方式是两码事。
7. 自测题(先自己答,再展开)
怪谁。RuntimeException 一派是程序自己的 bug(代码写对就不会发生),编译器放行;其余是外部世界不可控的风险(代码再好也可能发生),编译器强制你写预案。
看家谱:前者是 RuntimeException 的子类 → 运行时异常,编译器不管;后者直属于 Exception(不在 RuntimeException 之下)→ 编译时异常,不写预案编译不过。背家谱,别猜名字。
合理。运行时异常是纯代码 bug,编译器就算逼你 catch,你接住一个空指针也毫无意义——正确做法是改代码;而文件/网络这类外部风险无法靠改代码消除,只能靠预案兜底——不写预案的程序等于裸奔上路。
不是编译错误(语法全对),是受检异常没写预案的报错。解法两个:try-catch 接住,或方法签名加 throws 往上甩——5.3 正式学。本质区别:编译错误改语法,这个写预案。
NullPointerException、FileNotFoundException、ArithmeticException、ParseException、ClassCastException、IOException
运行时异常(编译器放行,运行才炸):NullPointerException、ArithmeticException、ClassCastException——全在 RuntimeException 家。编译时异常(编译器拦下,逼你写预案):FileNotFoundException、ParseException、IOException——Exception 直系,不在 RuntimeException 家。判断永远走家谱:RuntimeException 的子孙 → 放行;Exception 其余 → 拦下。
8. 下一步学什么
- ☑ 5.3 异常处理及语法 ✅ —— try-catch-finally 接、throw 主动甩、throws 声明甩,Unhandled exception 的两条活路落地。点此复习
相关笔记
- 5.1 什么是异常 ← 上一篇:异常对象与家谱,本节收网两个岔路口
- 4.4 多态 ← ClassCastException 的出生地(运行时异常派)
- 2.7 数组 / 3.2 类与对象 ← 越界与空指针的出生地
- 4.3 抽象类和接口 ← 家谱判断靠继承树:RuntimeException 的子孙才放行
- 📇 Java基础 · 索引
- 🏠 返回主页