接口默认方法声明throws受检异常时,实现类重写只能抛出其子类、不抛或改抛运行时异常,不得扩大异常范围,否则编译失败;这是为保障多态调用安全与里氏替换原则。

接口默认方法声明了 throws 受检异常(checked exception),实现类重写该方法时,不能抛出比接口中声明更宽泛的受检异常,但可以不抛、抛出子类异常,或只抛运行时异常。这是由 Java 的异常检查机制和多态契约决定的——子类方法不能破坏父类(或接口)承诺的异常边界。
必须遵守的异常声明规则
Java 要求重写方法的 throws 子句只能缩小或保持原范围,不能扩大。具体来说:
- 可以省略
throws(即不抛任何受检异常) - 可以抛出原异常的子类(如接口声明
throws IOException,实现类可写throws FileNotFoundException) - 可以同时抛出多个更具体的受检异常(只要都是原异常的子类)
- 不能新增其他受检异常(如接口没声明
SQLException,实现类就不能加) - 运行时异常(
RuntimeException及其子类)不受限制,可随意抛出
常见错误写法及编译报错
以下写法会直接编译失败:
// 接口定义
interface Service {
default void doWork() throws IOException {
// ...
}
}
错误示例:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
-
public void doWork() throws Exception→Exception比IOException更宽,编译报错 -
public void doWork() throws SQLException→ 与IOException无关的受检异常,不允许 -
public void doWork() throws IOException, InterruptedException→ 新增了未声明的受检异常,不合法
正确重写的几种实用方式
假设接口方法为 default void load() throws IOException,实现类可这样处理:
-
完全不抛受检异常:内部用 try-catch 处理掉,例如:
try { ... } catch (IOException e) { throw new RuntimeException(e); } -
抛出更具体的子类:如
throws FileNotFoundException(它是IOException的子类) -
包装后抛运行时异常:这是最常用做法,避免上层被强制处理,例如:
catch (IOException e) { throw new UncheckedIOException(e); } -
声明相同异常:直接写
throws IOException,语义清晰且兼容性最好
为什么设计成这样?
这是为了保障多态调用的安全性。调用方只依赖接口,它按接口声明做异常处理准备。如果实现类擅自扩大异常范围,调用方代码就可能因未捕获新异常而崩溃。JVM 在编译期强制校验,确保“里氏替换原则”在异常层面也成立——子类型必须能无缝替代父类型,包括异常行为。

















