自定义异常类必须继承Exception或RuntimeException;继承Exception为检查型异常,调用处须处理;继承RuntimeException为非检查型异常,编译器不强制处理。

自定义异常类必须继承 Exception 或 RuntimeException
Java 中没有“声明即可用”的自定义异常,必须显式定义一个类。关键区别在于:继承 Exception 是检查型异常(checked),调用处必须 try-catch 或声明 throws;继承 RuntimeException 是非检查型异常(unchecked),编译器不强制处理。
常见误写是直接 throw new Exception("msg") 试图模拟自定义行为——这仍是通用 Exception,无法体现业务语义,也不便于上层精确捕获。
正确做法:
public class InsufficientBalanceException extends RuntimeException {
public InsufficientBalanceException(String message) {
super(message);
}
// 可选:带 cause 构造器,方便链式异常追踪
public InsufficientBalanceException(String message, Throwable cause) {
super(message, cause);
}
}
抛出时用 throw,不是 throws
throws 是方法签名的一部分,用于声明可能抛出的异常类型;throw 才是实际触发异常的动作。新手常混淆二者,比如在方法体内写 throws new IllegalArgumentException() —— 这会编译报错。
立即学习“Java免费学习笔记(深入)”;
正确写法示例:
public void withdraw(double amount) {
if (amount > balance) {
throw new InsufficientBalanceException("余额不足:" + balance);
}
balance -= amount;
}
- 若该异常是
Exception子类,方法签名必须加throws InsufficientBalanceException - 若该异常是
RuntimeException子类,throws声明可省略 - 不要在
catch块里只写throw e;而不记录日志——原始堆栈已丢失上下文
捕获自定义异常要靠类型匹配,不是靠消息字符串
有人试图用 e.getMessage().contains("余额") 来判断是否为余额相关异常,这是脆弱且低效的设计。JVM 的异常分发机制依赖类型,不是字符串内容。
应使用多 catch 或 instanceof(Java 14+ 可用模式匹配):
try {
account.withdraw(1000);
} catch (InsufficientBalanceException e) {
logger.warn("用户{}提现失败:{}", userId, e.getMessage());
notifyUser(userId, "余额不足,请充值");
} catch (AccountFrozenException e) {
// 独立处理冻结逻辑
}
- 确保自定义异常类名能清晰表达业务含义,如
InvalidVerificationCodeException比BusinessException更具可读性 - 避免把所有业务错误都塞进同一个自定义异常类,靠字段或枚举区分——这削弱了类型系统的约束力
- 若需携带额外信息(如错误码、请求ID),应在异常类中定义
final字段并在构造时赋值
日志和监控中保留原始异常链很重要
生产环境排查问题时,最怕看到 Caused by: NullPointerException 被吞掉,只剩顶层包装异常。例如数据库连接失败引发 SQLException,再被包装成 PaymentFailedException,但没传入 cause,就断了根。
务必在自定义异常构造器中调用父类含 Throwable 参数的构造函数:
public class PaymentFailedException extends RuntimeException {
public PaymentFailedException(String message, SQLException cause) {
super(message, cause); // ← 关键:保留 cause
}
}
- SLF4J 的
logger.error("支付失败", e)会自动打印完整堆栈,前提是e的getCause()链未被截断 - APM 工具(如 SkyWalking、Pinpoint)依赖异常类型和 cause 链做错误聚类,缺失 cause 会导致同一类错误被分散统计
- 不要在
catch块里新建异常却不传cause,除非你明确想屏蔽底层细节










