throw是立即抛出异常实例,程序停在该行;throws是方法声明可能抛出的受检异常类型,仅编译期检查。throw后必须是异常对象,throws后只能是异常类名,可多个逗号分隔。

throw 是“真扔”,throws 是“预告要扔”
你写 throw 的那一刻,程序立刻停在那一行,异常对象被创建并上抛;而 throws 只是方法签名上的一行声明,编译器靠它检查调用链是否合规,运行时完全不生效。
-
throw后面必须是new XXXException("msg")这样的异常实例,不能是类名——写成throw IllegalArgumentException;直接编译报错 -
throws后面只能是异常类型(如IOException、SQLException),可多个,用逗号分隔;但声明了不代表一定会抛,只是“可能” - 一个方法里可以有多个
throw(比如不同 if 分支抛不同异常),但throws在方法头只出现一次
什么时候必须用 throws?——受检异常的硬性门槛
当你 throw 一个受检异常(checked exception),比如 FileNotFoundException 或 IOException,编译器会强制你处理:要么用 try-catch 包住,要么在方法声明加 throws 推给上层。漏掉就过不了编译。
- 运行时异常(
RuntimeException及其子类)不受此限:throw new NullPointerException()可以直接写,不用throws,也不用try-catch - 子类重写父类方法时,
throws声明的异常类型不能比父类更宽——比如父类声明throws IOException,子类只能throws FileNotFoundException(它是IOException子类)或不声明 - 常见误操作:在工具类方法里
throw new SQLException()却没加throws,结果编译失败,不是代码逻辑错,是契约没签好
throw 和 throws 经常一起出现,但职责绝不混用
典型协作场景是「底层资源操作 + 上层业务校验」:方法内部用 throw 主动拦截非法输入,同时用 throws 声明可能触发的 I/O 异常。
public String readConfig(String path) throws IOException {
if (path == null || path.trim().isEmpty()) {
throw new IllegalArgumentException("配置路径不能为空");
}
File file = new File(path);
if (!file.exists()) {
throw new FileNotFoundException("文件不存在: " + path);
}
return Files.readString(file.toPath()); // 可能抛 IOException
}
- 这里两个
throw都是运行时校验,不依赖throws;但最后一行调用可能抛IOException,所以方法头必须声明throws IOException - 调用方看到这个
throws,就知道要么自己try-catch,要么继续throws往上传——这是 Java 编译期强制的异常传递契约 - 别试图在
catch块里只写throw而不带异常对象(如catch (Exception e) { throw; }),Java 不支持这种“重新抛出”语法;正确写法是throw e;或throw new RuntimeException(e);
容易忽略的细节:throw 后的代码永远不会执行
这是最常踩的流程陷阱。只要执行到 throw,当前方法剩余语句全部跳过,哪怕它看起来“离得很近”。
立即学习“Java免费学习笔记(深入)”;
public void process(String input) {
System.out.println("start");
if (input == null) {
throw new IllegalArgumentException("input 为空");
System.out.println("这行永远不打印"); // 编译器会报错:unreachable statement
}
System.out.println("end"); // input != null 时才走到这里
}
- 编译器能检测到这种明显不可达代码并报错,但更隐蔽的是在循环或复杂条件分支中——比如
throw写在if最后一行,后面还跟着return或日志,结果日志永远不打 - 如果想在抛异常前记录上下文,必须把日志写在
throw之前,且确保不会被优化掉(比如某些 IDE 自动删“无用”语句) - 自定义异常时,别只依赖默认构造函数;带上
cause参数(如new BusinessException("订单创建失败", e)),否则原始堆栈信息会丢失
throw 是你按下暂停键,throws 是你把暂停键交到别人手上——交之前,得确认对方真有那个权限和能力按下去。






