Java要求方法调用受检异常必须显式声明throws或捕获,否则编译失败;声明时需按具体到宽泛排序子类在前、父类在后;throws RuntimeException冗余且易引发认知混淆与维护风险。

必须声明,否则编译直接失败——这是 Java 编译器的硬性契约,不是建议。
方法签名里没写 throws IOException,但调用了 new FileInputStream("x.txt")
编译器会报错:Unhandled exception type FileNotFoundException(FileNotFoundException 是 IOException 子类,属于受检异常)。你不能靠“我觉得不会出错”跳过它。
常见错误现象:
- 把
throws写进方法体里,比如{ throws IOException; }→ 报Syntax error on token "throws" - 把
throws放在返回类型前面,比如throws IOException String read()→ 报invalid method declaration; return type required - 调用方没处理,比如直接写
readFile();而不包try-catch,也不在自己方法签名加throws→ 同样编译失败
声明多个受检异常时,顺序和继承关系很关键
Java 不校验 throws 列表里的异常顺序,但人要看得懂,而且子类不能跟在父类后面声明。
正确写法示例:
void loadConfig() throws ConfigFileNotFoundException, EncodingException, IOException
错误写法示例:
void loadConfig() throws IOException, ConfigFileNotFoundException
因为 ConfigFileNotFoundException 继承自 IOException,后者已覆盖前者,编译器会报错:exception ConfigFileNotFoundException has already been caught(或类似提示)。
使用场景:
- 读文件时可能因路径错抛
FileNotFoundException,因编码错抛UnsupportedEncodingException,因磁盘满抛IOException→ 全部列出来,且按具体到宽泛排 - 接口方法声明
void save() throws SQLException,实现类可以只抛SQLTimeoutException(它是子类),但绝不能额外加IOException
在 main 方法里写 throws 是合法的,但通常不是好主意
public static void main(String[] args) throws IOException 能通过编译,JVM 会捕获并打印堆栈后退出。但这等于放弃对异常的响应能力。
容易踩的坑:
- 把
main当作“兜底出口”,结果上线后一个IOException导致整个服务静默崩溃,日志里只有堆栈,没有业务上下文 - 误以为写了
throws就算处理了异常——其实只是把责任推给 JVM,没做任何恢复、降级或告警 - 和 Spring 等框架混用时,
main抛异常可能绕过框架的统一异常处理器(如@ControllerAdvice),导致错误不被记录或转化
throws RuntimeException 是冗余的,但有人偏要写
像 void validate(String s) throws IllegalArgumentException 这种写法不报错,但编译器完全不强制,调用方也无需处理。它只起文档提示作用。
问题在于:
- 如果真这么写,别人会默认这是个受检异常,查 JDK 源码才发现是
RuntimeException子类,产生认知负担 - 一旦某天你把
IllegalArgumentException换成SQLException,却忘了改throws声明,编译就挂了——说明你把运行时异常和受检异常的边界搞混了 - 更隐蔽的坑:重写父类方法时,父类没声明
throws,你加了个throws IllegalArgumentException看似无害,但如果父类是接口,而其他实现类没这么写,多态调用时语义就不一致了
真正该警惕的,从来不是语法能不能写,而是调用链上每一层是否清楚自己承担哪部分异常责任——throws 是契约的起点,不是甩锅的终点。










