自定义异常 = 印一张自己的「事故报告」:继承 Exception 造类、补构造方法接消息、throw 上岗安检——它不是新语法,只是「继承 + throw/throws」的组合技。
4.1 继承(extends 的规矩)、5.3 异常处理及语法(throw 与 throws,误区里埋的钩子这节收网)、5.4 抛出异常(甩锅链条)。
1. 为什么需要"自定义"异常?
5.3 的安检版本我们这样甩:
if (age < 0 || age > 150) {
throw new Exception("年龄必须在 0~150 之间!");
}
功能没问题,但有两个别扭:
| 别扭点 | 说明 |
|---|---|
| 说普通话 | Exception 只能靠字符串区分"这是年龄问题还是余额问题",万一忘了写消息,抓瞎 |
| catch 没法精准匹配 | catch (Exception e) 什么都能接——想单独处理"年龄不合法"?接不住,一接全接 |
就像医院挂号:所有病人都写"不舒服",医生没法分诊。自定义异常 = 给每一种业务问题发一张专属病历,名字自带行话,catch 能精准分诊。
2. 诞生三步
以"年龄越界"为例:
第一步:继承 Exception(或 RuntimeException)
// 受检版:编译器会逼调用者表态(适合"外部风险"型问题)
public class AgeOutOfBoundsException extends Exception {
}
类名惯例:以 Exception 结尾,一眼认出这是异常类。继承谁决定待遇(收网 5.2 的两派知识):
- 继承 Exception → 受检异常,编译器盯着,调用者必须 try-catch 或 throws
- 继承 RuntimeException → 运行时异常,编译器放行,处理随缘
第二步:补两个构造方法
public class AgeOutOfBoundsException extends Exception {
public AgeOutOfBoundsException() {
super(); // 无参:不携带消息
}
public AgeOutOfBoundsException(String message) {
super(message); // 带参:把消息上交给父类存档
}
}
消息的旅程是:throw new Xxx("消息") → 你的构造方法收到 message → super(message) 上交给 Exception 父类,存进对象内部的 detailMessage 字段。将来 e.getMessage() 取的就是父类存的这份。
不写 super(message),消息就断在半路,getMessage() 返回 null——本节第一大坑。
第三步:throw 上岗 + catch 接住
public class Person {
private int age;
public void setAge(int age) throws AgeOutOfBoundsException { // 声明:可能甩出
if (age < 0 || age > 150) {
throw new AgeOutOfBoundsException("年龄 " + age + " 不合法,必须在 0~150 之间");
}
this.age = age;
}
}
public class Demo06 {
public static void main(String[] args) {
Person p = new Person();
try {
p.setAge(-5);
} catch (AgeOutOfBoundsException e) { // 精准分诊:只接这一种
System.out.println("安检拦截:" + e.getMessage());
}
}
}
安检拦截:年龄 -5 不合法,必须在 0~150 之间
对比 5.3 的版本:catch 里只接"年龄问题",别的异常该炸还炸——分诊能力就是自定义异常的核心价值。
3. 消息传递链(一图流)
「消息」从 throw 到 getMessage 的完整旅程:
- 甩出:
throw new AgeOutOfBoundsException("年龄-5不合法!")—— 消息塞进构造方法 - 接住:你的构造方法参数
String message收下 - 上交:
super(message)→ Exception 父类构造把它存进 detailMessage 字段(看不见但一直在) - 取货:catch 里
e.getMessage()从父类方法里取回
第二步如果偷懒不写构造方法,new AgeOutOfBoundsException() 也能编译通过——因为不写构造方法时类自动继承父类的无参构造(3.4 构造方法 的规矩)。但这时带消息的版本用不了,throw new Xxx("消息") 直接编译报错。所以规范写法是两个构造都补齐。
4. 受检还是运行时?选型口诀
| 继承谁 | 待遇 | 适合场景 |
|---|---|---|
extends Exception |
受检,编译器逼处理 | 调用者有能力预防或补救的业务风险:余额不足、年龄越界、文件不存在 |
extends RuntimeException |
运行时,随缘处理 | 属于使用者的 bug:传参传错了、非法状态——改代码才是正解 |
新手期一句话:拿不准就继承 Exception(受检)——编译器会替你检查每个调用者有没有表态,最不容易漏。
5. 动手环节(IDEA05 · Demo06)
- 新建包
com.itheima.exception05,先写AgeOutOfBoundsException(继承 Exception + 两个构造)。 - 写
Person.setAge(throws + throw 自定义异常),main 里 try-catch 打印e.getMessage()。成功标准:控制台打印"安检拦截:年龄 -5 不合法…"。 - 30 秒实验 ①:把
AgeOutOfBoundsException(String message)里的super(message)注释掉 → 编译通过,但getMessage()打印 null。 - 30 秒实验 ②:改成继承
RuntimeException→setAge的 throws 和 main 的 try-catch 全删也能编译(运行时待遇,收网 5.2)。 - 30 秒实验 ③:再定义一个
ScoreOutOfBoundsException,在 main 里故意只 catch 它却触发年龄异常 → 按住 Ctrl 看编译器报错——catch 接不住的受检异常,编译器照样拦。
6. 常见误区
- 忘了 super(message) → 消息断传,
getMessage()返回 null。这是第一大坑,30 秒实验 ① 亲眼看过一次就记住了。 - 以为自定义异常是"新语法" → 不是。它就是 4.1 继承 + 5.3 throw/throws 的组合技,没有任何新关键词。
- catch 自定义异常时以为"名字像就能接" → catch 按家谱匹配(5.3 规矩),类型对不上照样接不住。
- 继承 Exception 却忘了 throws → 受检异常甩出必须声明,编译器当场报 Unhandled exception。
- 到处自定义、滥用异常 → 简单的参数错误用 IllegalArgumentException 之类的现成异常就够了;自定义留给业务专属、需要分诊的问题。
7. 本节小结
| 要点 | 内容 |
|---|---|
| 是什么 | 继承 Exception/RuntimeException 造的业务专属异常类 |
| 三步 | 继承 → 补构造(super(message) 上交消息)→ throw 上岗 |
| 核心价值 | catch 精准分诊 + 报错信息自带行话 |
| 选型 | 业务风险 → Exception;使用者的 bug → RuntimeException |
| 命名 | XxxException 后缀 |
8. 可折叠自测题
继承 Exception(受检):调用方必须 try-catch 或 throws,否则编译报错;继承 RuntimeException(运行时):调用方写不写处理都能编译,不处理则运行时炸。
自定义类里没写带 String 参数的构造方法。空壳类只自动继承父类无参构造,带消息的版本得自己补 Xxx(String message) { super(message); }。
先查构造方法里是不是漏了 super(message)——消息没上交给父类存档;再确认甩出时确实传了消息而不是 new Xxx()。
public class BalanceNotEnoughException extends Exception {
public BalanceNotEnoughException() { super(); }
public BalanceNotEnoughException(String message) { super(message); }
}
public void withdraw(double amount) throws BalanceNotEnoughException {
if (amount > balance) {
throw new BalanceNotEnoughException("余额不足:当前 " + balance + ",请求 " + amount);
}
balance -= amount;
}
它是 Exception/RuntimeException 家谱上的新枝叶——用继承挂上去,用 throw 甩出,用 catch 精准接住;甩锅链条、两派待遇、消息机制全部照用父类那一套。
9. 下一步学什么
- ☑ 5.6 本章小结 ✅ —— 五节串线收官:速查 12 条 + 模板 4 段 + 七大名坑 + 安检演进线 + 毕业自检。点此复习
相关笔记
- 5.4 抛出异常 ← 上一篇:甩锅链条与两派待遇,本节把"甩"的资格升级成自己的类
- 5.3 异常处理及语法:throw/throws 的本体,自定义异常的组合技素材
- 5.2 运行时异常与编译时异常:继承谁决定待遇的依据
- 4.1 继承:extends 的规矩,自定义异常的骨架来源
- 3.4 构造方法:为什么空壳类能 new、super(message) 在干嘛