子类异常必须写在父类异常之前,否则因jvm按顺序匹配导致父类提前捕获所有子类异常,造成后续catch不可达而编译失败。

在多重 catch 块中,子类异常必须写在父类异常之前,否则编译不通过——这是 Java 强制的语法约束,本质是为了避免“不可达代码”。
为什么子类异常必须放在父类前面
Java 虚拟机按 catch 块从上到下的顺序匹配异常类型。一旦某个 catch 参数能接收当前异常(即异常实例是该参数类型的实例或子类),就进入该块执行,后续 catch 不再检查。
如果父类写在前面(比如 Exception),那么所有其子类异常(如 IOException、SQLException)都会被它提前捕获,导致后面的子类 catch 永远无法执行——这就是“不可达代码”,编译器直接报错。
常见错误写法及修正
以下写法会编译失败:
try {
// 可能抛出 IOException
} catch (Exception e) { // ❌ 父类在前,覆盖所有子类
System.out.println("通用异常");
} catch (IOException e) { // ❌ 编译错误:Unreachable catch block
System.out.println("IO异常");
}
正确顺序应为:
- 更具体的异常(子类)写在上面
- 更宽泛的异常(父类或顶层异常)写在下面
- 同级异常(无继承关系)顺序任意,但建议按业务频次或语义分组排列
实际开发中的推荐写法
兼顾可读性与健壮性,建议按如下层次组织:
- 业务自定义异常(如
UserNotFoundException) - 标准受检异常(如
SQLException、IOException) - 标准非受检异常(如
IllegalArgumentException、NullPointerException) - 兜底的
Exception或Throwable(仅限日志记录或统一处理,慎用)
示例:
try {
userDao.save(user);
} catch (UserAlreadyExistsException e) {
log.warn("用户已存在", e);
} catch (SQLException e) {
log.error("数据库操作失败", e);
throw new ServiceException("保存失败", e);
} catch (IllegalArgumentException e) {
log.warn("参数校验失败", e);
} catch (Exception e) {
log.error("未预期异常", e);
throw new UnexpectedException(e);
}
小技巧:利用多异常捕获简化代码(Java 7+)
若多个异常需执行相同逻辑,可用竖线分隔写在同一个 catch 中:
- 要求这些异常类型之间不能有继承关系(否则仍会触发顺序问题)
- 适用于处理方式一致、且语义相近的异常,例如多种 IO 相关异常
例如:
catch (FileNotFoundException | SecurityException e) {
log.warn("文件访问异常", e);
}
注意:FileNotFoundException 是 IOException 的子类,而 SecurityException 是 RuntimeException 的子类,二者无继承关系,可以并列。










