钩子:Java 不懂你的业务
Java 内置异常一大堆,但没有一个叫"年龄不合法",也没有一个叫"余额不足"。
于是新手常写:
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
能用,但有两个代价:调用方 catch (IllegalArgumentException) 时,"参数传错"和"年龄规则错"混在一起,想单独处理"年龄问题"做不到;报错信息全靠手拼字符串,东一处西一处,格式还不统一。
自定义异常,就是用 Java 的异常机制说自己的业务黑话。
核心:3 行造一个异常
异常本质也是个类,继承一下就有了:
// 运行时异常款(不强制 try,适合"编程错误"类)
class AgeOutOfBoundsException extends RuntimeException {
public AgeOutOfBoundsException(String message) {
super(message); // 把信息交给父类,getMessage() 才能取到
}
}
用起来:
static void setAge(int age) {
if (age < 0 || age > 150) {
throw new AgeOutOfBoundsException("年龄不能为负数或超 150,收到 " + age);
}
}
调用方就可以精准捕获这一种业务问题:
try {
setAge(-5);
} catch (AgeOutOfBoundsException e) {
System.out.println(e.getMessage()); // 年龄不能为负数或超 150,收到 -5
}

进阶:异常对象还能带数据
这是"会说话"的完全体——异常不只是字符串,它是个正经对象,想带什么字段都行:
class BalanceNotEnoughException extends Exception { // 受检款
private final double shortage; // 缺多少钱
public BalanceNotEnoughException(double shortage) {
super("余额不足,还差 " + shortage + " 元");
this.shortage = shortage;
}
public double getShortage() { return shortage; }
}
// 调用方拿到差价,直接弹充值提示:
catch (BalanceNotEnoughException e) {
showRechargeDialog(e.getShortage());
}
报错和业务数据一起送达,调用方拿到手就能用——这才是异常机制的正确打开方式。
决策:继承谁?什么时候用?
回顾第 28 篇的判断标准,自定义异常完全通用:
- 继承 RuntimeException:编程错误类(参数传错、状态不对)——编译器不强制,别到处 try,改代码根治;
- 继承 Exception(受检):调用方必须面对的业务分支(余额不足、文件不存在)——编译器逼着处理,处理方式可能就是"提示用户充值"。
顺手厘清一对万年混淆:
- throw 是动作,在方法体内真正抛出一个异常对象;
- throws 是声明,写在方法签名上,提前打招呼"我这方法可能炸"。
速查表
| 要素 | 写法 | 记忆点 |
|---|---|---|
| 定义 | class XxxException extends RuntimeException |
3 行:继承 + 构造 + super(message) |
| 抛出 | throw new XxxException("人话") |
throw 后面跟对象 |
| 声明 | void pay(...) throws BalanceNotEnoughException |
throws 后面跟类型 |
| 带数据 | 自定义字段 + getter | 异常对象 = 会说话的数据包 |
| 选型 | 编程错误→RuntimeException;业务分支→Exception | 同第 28 篇标准 |
本文示例代码均已本地实测通过,可直接运行。
对应文件:CustomExceptionDemo.java
橙码酱,分享更有用的技术知识,助你成为更优秀的开发者。