子类重写方法时抛出的异常不能比父类更宽泛,只能抛出父类声明异常的子类或不抛异常;若父类未声明异常,子类不得添加checked exception,但可抛RuntimeException及其子类。

子类重写方法时抛出的异常不能比父类更宽泛
Java 编译器会直接拒绝这种写法:如果父类方法声明了 IOException,子类重写时却抛出 Exception,编译失败。这不是运行时问题,是编译期强制约束——因为调用方只承诺处理父类声明的异常范围,子类扩大范围就破坏了契约。
- 父类方法抛
IOException,子类只能抛IOException及其子类(如FileNotFoundException),或不抛异常 - 父类方法没声明异常(即不抛 checked exception),子类重写也不能加
throws IOException,否则编译报错Unhandled exception type IOException - runtime exception(如
NullPointerException)不受此限,子类可自由添加、删除或替换
为什么 throws Exception 在子类里总是危险的
看似“兜底”的写法,实际让所有调用链失去异常控制权。父类接口说“可能 IO 出错”,你改成“可能任何事出错”,调用方原本 catch IOException 的逻辑就失效了,要么补 catch Exception(掩盖问题),要么干脆不管(崩溃)。
- 常见错误现象:
Exception被 catch 后吞掉日志,导致线上故障难定位 - 使用场景中,只有明确需要向上透传未预期异常时才考虑
RuntimeException包装,而非直接 throwsException - IDE 常提示 “Redundant ‘throws’ declaration”,就是提醒你这行代码违背了 Liskov 替换原则
@Override 方法里 throw 子类异常的实操边界
可以抛更具体的异常,但必须确保调用方无需修改原有异常处理逻辑。比如父类抛 SQLException,子类抛 SQLTimeoutException 是安全的,因为它是前者的子类,原有 catch SQLException 依然生效。
- 参数差异:异常类型是否为父类异常的直接/间接子类,用
isAssignableFrom()可验证 - 性能无影响,这是纯编译期检查,不产生额外字节码
- 容易踩的坑:误以为“更具体”等于“更少”,其实
IOException和SQLException是平行关系,不能互相替代 - 示例:
void read() throws IOException { ... }<br>→ 子类可写:void read() throws FileNotFoundException { ... }但不能写void read() throws Exception { ... }
父类没声明异常,子类想报错怎么办
不能加 throws,但可以抛 unchecked exception。这是标准解法:把业务上必须通知调用方的错误,包装成 RuntimeException 子类(如自定义 ValidationFailedException)。
立即学习“Java免费学习笔记(深入)”;
- 常见错误现象:强行加
throws IOException导致编译失败,然后删掉 try-catch,静默吞异常 - 使用场景中,校验失败、状态非法、配置缺失等,都适合转成 runtime exception
- 注意不要滥用:频繁抛
RuntimeException会让调用方失去对错误流的掌控,应配合文档或注释说明触发条件 - 示例:
void save(User u) {<br> if (u == null) throw new IllegalArgumentException("user cannot be null");<br>}
最易被忽略的是:异常类型的继承关系不是看名字像不像,而是看 class loader 加载的实际类型树。哪怕两个模块各自定义了同名的 MyException,只要不在同一继承链上,就不能互换。










