throws必须声明在方法签名末尾,且仅对受检异常(如IOException、SQLException)强制要求;非受检异常(如RuntimeException)无需声明,冗余添加不推荐。

throws 必须声明在方法签名末尾,且只对受检异常有效
Java 中 throws 不是可选修饰符,而是语法强制要求:只要方法体内可能抛出 **受检异常(checked exception)** 且未用 try-catch 捕获,就必须在方法签名后显式声明。否则编译直接报错 Unhandled exception type XXXException。
它不适用于 RuntimeException 及其子类(如 NullPointerException、IllegalArgumentException),这些属于非受检异常,编译器不强制处理,加 throws 也不会报错,但属于冗余写法,通常不推荐。
- 常见受检异常:
IOException、SQLException、ClassNotFoundException - 声明多个异常用逗号分隔:
throws IOException, SQLException - 子类方法重写时,
throws声明的异常类型不能比父类更宽(即只能相同或更具体)
声明 throws 后,调用方必须处理,否则编译失败
一旦方法 A 声明了 throws IOException,所有直接调用 A 的代码就进入“责任传递链”——要么自己用 try-catch 捕获 IOException,要么在自己的方法签名中也加 throws IOException 向上抛。
这是 Java 编译期强制的错误传播契约,不是运行时行为。没有这个声明,编译器根本不会让你通过。
- 错误写法:
FileReader fr = new FileReader("a.txt");(没包try,也没声明throws)→ 编译报错 - 正确写法 1(捕获):
try { ... } catch (IOException e) { ... } - 正确写法 2(继续抛):
void read() throws IOException { ... }
不要滥用 throws 把所有异常都往上推
把 throws Exception 或 throws Throwable 写在公共方法上,看似省事,实则破坏接口契约:调用方无法预判具体风险类型,被迫写泛化 catch,掩盖真正需要区分处理的错误场景。
比如数据库操作方法应明确声明 throws SQLException,而不是笼统抛 Exception;文件读取应声明 throws IOException,而非吞掉或转成 RuntimeException 匿名抛出(除非你有明确封装策略)。
- 过度声明会污染 API,让使用者失去错误分类能力
- 某些框架(如 Spring)会自动将受检异常转为非受检异常(如
DataAccessException),此时原生throws SQLException反而成了干扰项 - 若真要统一兜底,应在最外层(如 Controller 层)集中处理,而不是每层都
throws Exception
与 throw、try-catch 的协作关系容易混淆
throws 是声明“我可能会抛”,throw 是实际“我现在就抛”。二者常共存:方法里用 throw new IOException(),同时方法签名得配 throws IOException;但也可以只有 throws(依赖底层调用抛出),而方法体中不出现 throw 语句。
注意:一个方法可以有 throws,但内部完全不写 throw——只要它调用了其他声明了该异常的方法,且自己没捕获,就满足编译要求。
- 合法示例:
void copy(File src, File dst) throws IOException { Files.copy(src.toPath(), dst.toPath()); }(Files.copy自带throws IOException,此处只是传递) - 非法示例:
void f() throws IOException { System.out.println("ok"); }(无任何可能抛出IOException的逻辑,编译器警告“unreachable catch block”) -
try-catch和throws是互斥选择:同一异常在同一作用域内,不能既捕获又声明抛出
真正难的不是语法,是判断哪些异常该由当前层消化(比如重试、降级、日志),哪些该交出去(比如权限不足、资源不可达)。这取决于你的抽象层级和错误处理策略,而不是 throws 写不写得出来。

















