5.1 什么是异常
异常 = 程序运行中冒出来的"意外状况",它会打断正常流程。编译能过的代码不代表能跑完——运行时一遇意外,JVM 就现场造出一个异常对象(一份写明类型、消息、位置的事故报告)。4.4 集齐的三大新手异常,本章开始收容。
1. 先分清"三种不对劲"
学异常前先划清界限——"程序出问题"其实有三种,待遇完全不同:
| 类型 | 什么时候暴露 | 典型例子 | 谁来管 |
|---|---|---|---|
| 编译错误 | 编译期,javac 直接拦下 | 少分号、类名拼错、类型不匹配 | 编译器,不改不让跑 |
| 异常(本章主角) | 运行期,编译能过 | 除零、空指针、数组越界 | 运行时才炸,Java 提供处理机制 |
| 逻辑错误 | 运行完才发现结果不对 | 该用 >= 写成了 > |
没人管,最阴险,靠测试和调试 |
1.4 学过"编译型语言编译期就能抓语法错"——但编译器只能查规矩错,查不了路况。10 / 0 语法完全合法,编译器没意见;真跑起来才知道,除数是 0,数学上就不成立。异常管的正是这类"语法没错、路况出了意外"的情况。
2. 生活类比:爆胎不是"车造错了"
开车上班是"正常流程",路上爆胎是"意外状况":
- 爆胎不代表你不会开车、也不代表车是坏的(不是编译错误、不是逻辑错误);
- 但它打断了你上班的流程——你必须停下来处理;
- 处理方式有两种:自己换备胎继续走(捕获处理),或者打电话叫救援,让更高层的人来管(往上抛)。
Java 对异常的态度一样:意外可以发生,但不能不了了之——每次意外都必须有人写一份"事故报告"并交给该管的人。这份报告,就是异常对象。
3. 异常在 Java 里的真身:也是对象
看一次真事故。运行下面代码:
public class Demo01 {
public static void main(String[] args) {
System.out.println("程序开始");
int r = 10 / 0; // 第 5 行:炸点
System.out.println("程序结束"); // 永远执行不到
}
}
控制台输出(红字):
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Demo01.main(Demo01.java:5)
保姆式解读这份报错——三段拆开看:
| 报错片段 | 是什么 |
|---|---|
java.lang.ArithmeticException |
异常类型:算术异常类(JDK 自带的类) |
/ by zero |
异常消息:具体事故原因——除以了零 |
at Demo01.main(Demo01.java:5) |
发生位置:Demo01 类的 main 方法,第 5 行(这叫堆栈跟踪) |
关键是想通一件事:这行红字不是凭空打印的,它是 JVM 现场造出的一个对象——当程序跑到 10 / 0 这一刻:
- JVM 发现意外(除零);
- 现场创建一个
ArithmeticException类的对象,把类型、消息、发生位置装进去; - 把这个对象抛出(throw)——像把事故报告往上递;
- 没人接住 → 报告打印到控制台 → 程序立即中断,第 6 行的"程序结束"永远不执行。
所以你看到的一切异常报错,本质都是某个异常类的对象。异常也是类、也能 new——4.1 学的"万物继承 Object"、4.3 的继承体系,在这里正式上岗:所有异常类都是一家人(下面看家谱)。
4. 异常体系家谱(会认层次,5.2/5.3 反复用)
Throwable(一切"可抛物"的老祖宗)
├── Error —— 严重错误:JVM 级的,程序管不了(如内存耗尽)
└── Exception —— 异常:程序可以处理(本章主角)
├── RuntimeException —— 运行时异常:编译器不强制处理
│ ├── ArithmeticException 除零
│ ├── NullPointerException 空指针(3.2 老朋友)
│ ├── ArrayIndexOutOfBoundsException 越界(2.7 老朋友)
│ └── ClassCastException 类型转换失败(4.4 老朋友)
└── 其他 Exception —— 编译期异常(受检异常):编译器逼你处理(5.3 见)
家谱上的两个岔路口,判断标准就一条:
- Error:房子塌了——内存耗尽、JVM 崩溃。程序层面不该管也管不了,看到了说明环境有大问题;
- Exception:路上爆胎——可以换备胎继续走。异常处理的全部内容都在这一支。
你炸过的三大新手异常全是 RuntimeException 家的孩子——特点是编译器不拦,运行才炸,所以叫运行时异常。而另一支(如文件找不到、网络断了)编译器强制你写处理代码,不写连编译都过不了——所以叫"受检异常",5.2 学 try-catch 时会亲眼见到编译器怎么逼你。
5. 异常不处理会怎样:程序当场中断
再用一个实验看清"中断"的杀伤力:
public class Demo02 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // 越界:最大索引才 2
System.out.println("重要收尾工作"); // 执行不到!
}
}
越界异常一抛出,main 立刻中断——后面的代码一行都不会跑。如果"收尾工作"里是保存文件、释放资源,那就真出事故了。这就是异常必须被"管理"的原因:不是让报错好看点,而是保证意外发生时程序还能体面地收场。
6. 动手:亲手制造并阅读一次事故
新建项目 IDEA05,新建 Demo01(照第 3 节抄),运行并完成三个实验:
三个 30 秒实验
- 读报告:认真看报错红字,用手指分别指出异常类型、消息、位置三段——读报错是本章的核心技能,比背概念重要十倍。
- 改炸点:把
10 / 0改成int[] arr = new int[3]; arr[5] = 1;——报错类型变成ArrayIndexOutOfBoundsException,报告格式一模一样。 - 看中断:在炸点后面再加一行打印,运行确认它永远不执行——体会"异常 = 流程被打断"。
7. 常见误区
语法错是编译错误,异常是运行时的意外——很多异常和"写错"无关:用户输错输入、要读的文件不存在、网络突然断了。异常是 Java 管理"计划外状况"的机制,不是丢人的标记。
三大新手异常(空指针/越界/转型失败)全是 RuntimeException——编译器一个都不拦,全靠运行时才炸。编译通过 ≠ 运行没问题。
异常若没人处理,当场中断——炸点之后的所有代码(哪怕逻辑完全无关)都不会执行。"只是控制台红了点"是最危险的错觉。
Error 和 Exception 是两兄弟:Error 是 JVM 级的严重错误(如内存耗尽),程序层面不该管也管不了。日常说的"异常处理"只针对 Exception 这一支。
恰恰相反——它是把"意外"接住后体面处理:记日志、给用户友好提示、释放资源,然后让程序继续跑。不处理才是真把问题藏进了"程序崩溃"。下一节就学。
8. 自测题(先自己答,再展开)
编译错误:编译期被拦,如少分号;异常:编译能过、运行时炸,如除零;逻辑错误:程序跑完结果不对,如把 > 写成 >=。前两种有报错可查,逻辑错误最阴险。
是一个对象(异常类的实例,JVM 现场创建并抛出)。三样:异常类型(哪个类的对象)、异常消息(具体原因,如 / by zero)、发生位置(堆栈跟踪,类、方法、行号)。
立即中断——异常抛出点之后的代码一行都不执行,报告打印到控制台。所以异常处理的真正目标不是"让报错好看",而是保证意外发生时程序能体面收场(释放资源、保存数据、给出提示)。
Error 是 JVM 级严重错误,管不了也不用管;Exception 是可处理的异常,是本章主角。三大新手异常(NPE / 越界 / ClassCastException)都是 Exception → RuntimeException 这一支的,特点是编译器不拦、运行时才炸。
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Calc.divide(Calc.java:8)
at Calc.main(Calc.java:3)
异常类型 ArithmeticException(算术异常),消息 / by zero(除数为零)。位置读法从上往下:直接炸点在 Calc.divide 第 8 行,它是被 main 第 3 行调用的——堆栈跟踪就是"事故现场的呼叫链",最上面一行是炸点本身,往下依次是"谁调出来的"。
9. 下一步学什么
- ☑ 5.2 运行时异常与编译时异常 ✅ —— 两派划分标准(怪谁):RuntimeException 编译器放行 vs 受检异常编译器逼你写预案。点此复习
相关笔记
- 4.4 多态 ← 三大新手异常在此集齐,本章开收容所
- 3.2 类与对象 ← 空指针异常的出生地
- 2.7 数组 ← 数组越界异常的出生地
- 1.4 Java程序的运行机制 ← 编译期 vs 运行期的分界线,三类错误的裁判规则
- 📇 Java基础 · 索引
- 🏠 返回主页