运行时异常(如NullPointerException、ArrayIndexOutOfBoundsException)继承RuntimeException,编译器不强制处理,暴露逻辑错误,须修复而非捕获;检查异常(如IOException、SQLException)继承Exception且非RuntimeException,编译器强制处理,代表可恢复的外部故障,需业务决策。

运行时异常(RuntimeException)有哪些典型表现
Java中运行时异常继承自 RuntimeException,编译器不强制要求处理,但一旦抛出往往意味着程序逻辑错误,必须修复而非简单捕获。
常见类型包括:
-
NullPointerException:调用null引用的实例方法或访问字段 -
ArrayIndexOutOfBoundsException:数组下标越界,如访问arr[5]但arr.length == 3 -
ClassCastException:非法强制类型转换,例如将String强转为Integer -
IllegalArgumentException:方法传入非法参数,如Thread.sleep(-1) -
ConcurrentModificationException:遍历集合时被其他线程或同一线程非迭代器方式修改
这类异常通常暴露的是编码疏漏,比如未判空、边界计算错误、并发控制缺失。不建议用 try-catch 掩盖,而应前置校验或重构逻辑。
检查异常(Checked Exception)为什么必须显式处理
检查异常继承自 Exception 但不继承 RuntimeException,编译器强制要求:要么用 try-catch 捕获,要么在方法签名加 throws 声明。这是 Java 对“可恢复外部故障”的契约式设计。
立即学习“Java免费学习笔记(深入)”;
典型例子:
-
IOException:文件读写、网络连接失败等 I/O 操作异常 -
SQLException:数据库操作执行失败,如 SQL 语法错误、连接中断 -
InterruptedException:线程被中断,常见于Thread.sleep()或Object.wait()
关键点在于:它们代表程序**本不可控但预期可能发生的状况**。忽略处理会导致编译失败,不是疏忽,而是语言机制在提醒你——这个错误需要业务层面决策:重试?降级?记录日志并通知用户?
如何判断一个异常该捕获还是声明抛出
核心看两点:当前方法能否合理响应,以及调用方是否具备上下文做决策。
例如处理文件读取:
public String readFile(String path) throws IOException {
// 不在此处 try-catch,因为:
// - 无法确定路径无效是临时故障还是永久错误
// - 调用方可能选择重试、换路径、或返回友好提示
return Files.readString(Paths.get(path));
}而下面这段就属于误用:
public void processUser(User user) {
try {
sendEmail(user.getEmail()); // 抛出 checked exception
} catch (MessagingException e) {
// 仅打印日志,没补偿、没告警、没回滚
logger.error("email failed", e);
}
}这种“吞异常”掩盖了系统可靠性问题。正确做法是:捕获后至少做一件事:记录、补偿、转换为更上层能理解的异常(如自定义 UserNotificationFailedException),或直接向上抛出。
自定义异常该继承 RuntimeException 还是 Exception
取决于你希望调用方如何应对它。
- 继承
RuntimeException:表示这是“不该发生但发生了”的编程错误,如InvalidOrderStateException(订单状态非法流转) - 继承
Exception:表示这是“可能发生且需业务处理”的场景,如InsufficientBalanceException(余额不足,前端需提示充值)
注意:不要为了省事全用运行时异常;也不要为了强制处理把所有异常都设为检查异常。Spring 的 DataAccessException 体系就是反例——它把原本检查的 JDBC 异常统一转为运行时异常,交由全局异常处理器统一响应 HTTP 状态码,反而提升了可维护性。
真正容易被忽略的是异常语义的粒度:同一个 Exception 子类不该既用于网络超时又用于参数校验失败。区分清楚,才能让 catch 块做真正有意义的事。










