子类重写父类方法时,throws声明的受检异常只能是父类声明的受检异常的子类型或不抛出,不可扩大范围;运行时异常和错误无此限制,以保障里氏替换原则。

在子类重写父类方法时,不能扩大 throws 声明的异常范围,但可以缩小或不抛出异常。这是 Java 的“异常检查规则”(checked exception rule)所要求的——子类方法的 throws 子句只能声明父类方法中已声明的**受检异常(checked exception)的子类型**,或者完全不声明(即更严格),而不能新增、也不能用更宽泛的异常类型替代。
必须遵守的 throws 限制规则
Java 编译器强制要求:子类重写方法的 throws 声明,其列出的**受检异常类型必须是父类方法 throws 列表中对应异常的子类或相同类型**;对于运行时异常(RuntimeException 及其子类)和错误(Error),无此限制(因为它们无需声明)。
- ✅ 允许:父类 throws IOException → 子类 throws FileNotFoundException(IOException 的子类)
- ✅ 允许:父类 throws IOException → 子类不写 throws(即不抛出任何受检异常)
- ❌ 不允许:父类 throws IOException → 子类 throws Exception(Exception 是 IOException 的父类)
- ❌ 不允许:父类 throws IOException → 子类 throws SQLException(与 IOException 无关的受检异常)
- ⚠️ 注意:运行时异常(如 IllegalArgumentException)可随意添加或删除,编译器不检查
实际写法示例
假设父类定义如下:
public class Service {
public void process() throws IOException, SQLException {
// ...
}
}
子类重写时合法的 throws 写法包括:
-
public void process() throws IOException { ... }(只保留其中一个,且是原类型) -
public void process() throws FileNotFoundException { ... }(IOException 的子类) -
public void process() { ... }(不抛出任何受检异常) -
public void process() throws IOException, IllegalArgumentException { ... }(可额外抛出 RuntimeException 子类)
但以下写法会编译失败:
public void process() throws Exception { ... } // ❌ 编译错误:Exception 范围大于 IOException
为什么这样设计?
这是为了保障里氏替换原则(LSP):调用方只依赖父类签名编写代码,如果子类突然抛出父类没声明的受检异常,调用方就无法通过编译期检查来处理它,破坏了接口契约的稳定性。
- 调用方写了
try { service.process(); } catch (IOException e) { ... } - 若子类偷偷抛出 SQLException,这段代码就无法捕获,也不符合编译预期
- 所以 Java 强制子类只能“更守规矩”,不能“更出格”
绕过限制的常见做法(慎用)
当业务逻辑确实需要抛出新异常,又受限于 throws 规则时,可考虑:
- 将新异常包装为父类已声明异常的子类(如自定义
MyIOException extends IOException) - 用运行时异常替代(如
throw new IllegalStateException("xxx", cause)),但需确保调用方能接受这种非强制处理方式 - 在方法内部消化异常(如记录日志 + 返回错误码/Optional),避免向外抛出

















