子类重写方法时,检查异常只能缩小或不变,不可扩大;运行时异常无此限制。因父类声明的异常是调用方编译期捕获依据,违反里氏替换原则会导致运行时崩溃。

因为这会破坏多态调用的安全性——父类引用调用方法时,只承诺处理父类声明的异常;若子类偷偷抛出更宽泛的检查异常,调用方根本无法在编译期预见和捕获,导致程序在运行时崩溃。
核心是里氏替换原则(LSP)
子类对象必须能安全替代父类对象。如果父类方法只声明 throws IOException,那所有使用该父类类型的代码,都只准备处理 IOException 及其子类。一旦子类改成 throws Exception,调用方原代码中 catch (IOException e) 就漏掉了其他可能异常,编译器无法预警,运行时直接抛出未捕获异常。
检查异常受编译期强制约束
- 父类没写 throws → 子类不能加任何检查异常(如 IOException、SQLException)
- 父类 throws IOException → 子类只能 throws FileNotFoundException(子类)、SecurityException(不行,不是 IOException 子类)、或干脆不写 throws
- 父类 throws ParseException → 子类可 throws DateTimeParseException(它是 ParseException 的子类),但不能 throws IOException(无关且更宽)
运行时异常不受限,但有实践建议
RuntimeException 及其子类(如 IllegalArgumentException、NullPointerException)不需要声明,也不参与 throws 检查。子类可以自由 throw new SecurityException() 或自定义 ValidationFailedException extends RuntimeException,无需修改 throws 子句。
不过要注意:别用 catch (Exception e) { throw new RuntimeException(e); } 全局包装——这会吞掉 InterruptedException 等需响应中断的异常,影响线程协作。
接口实现也遵循同样规则
接口方法 void parse() throws ParseException,实现类不能 throws IOException,哪怕两者都是检查异常。它只允许:
- 不抛检查异常(即方法体里全 try-catch 处理掉)
- 抛 ParseException 的子类(如 DateTimeParseException)
- 抛运行时异常(任意)
违反时编译器报错类似:error: overridden method does not throw java.io.IOException 或 Exception is not compatible with throws clause。










