子类重写父类方法时抛出的异常不能比父类更宽:只能抛出父类声明的异常或其子类,不可新增或扩大检查型异常范围;运行时异常不受限,可用作安全“逃生通道”;设计上应避免过度使用受检异常,优先考虑统一错误处理机制。

Java中子类重写父类方法时,抛出的异常类型不能比父类方法声明的异常范围更宽——这是编译器强制执行的规则,目的是保证多态调用的安全性。简单说:子类方法只能抛出父类方法声明的异常,或其子类(更具体的异常),不能新增检查型异常(checked exception),也不能抛出更宽泛的异常类型。
理解“异常范围不可超过”的含义
这里的“范围”指异常类型的继承关系。比如父类方法声明 throws IOException,子类重写时可以:
- 不抛出任何异常(即不写 throws)
- 抛出 IOException 的子类,如 FileNotFoundException
- 抛出运行时异常(RuntimeException 及其子类),因为它们无需在方法签名中声明,也不受该规则限制
但不能:
- 抛出 Exception 或 Throwable(比 IOException 更宽)
- 新增另一个检查型异常,如同时 throws IOException, SQLException
常见错误及修正方式
编译报错 “error: overridden method does not throw ...” 通常是因为子类试图扩大异常范围。解决思路是:
- 把新增的检查型异常转为运行时异常:例如将 throws SQLException 改为 throw new RuntimeException(e) 包装后抛出
- 在子类方法内部处理掉额外异常:用 try-catch 捕获并转换、记录或降级处理,避免向外抛出
- 调整父类方法的 throws 声明(需谨慎):如果设计允许,可让父类提前声明更通用的异常,使子类有扩展空间
运行时异常是安全的“逃生通道”
RuntimeException 及其子类(如 IllegalArgumentException、NullPointerException)不受该规则约束,因为它们不要求调用方显式处理。所以当子类逻辑可能引发父类未声明的新问题时,常用这种方式保持契约兼容:
@Override
public void save(User user) throws IOException {
try {
db.insert(user); // 可能抛 SQLException
} catch (SQLException e) {
throw new RuntimeException("DB insert failed", e);
}
}
这样既没违反重写规则,又如实反映了执行失败的原因。
设计建议:优先用受检异常表达契约,但别过度
父类方法的 throws 声明本质上是对调用方的契约承诺。如果未来多个子类都需要抛不同异常,说明父类抽象不够合理。此时更优做法是:
- 将父类方法设计为不抛检查型异常,由子类自行决定错误传播方式
- 用返回值(如 Result
)或回调统一处理错误,解耦异常声明 - 对必须暴露的业务异常,定义统一的业务异常基类,并让所有子类抛其子类
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











