throw是语句,用于方法体内抛出具体异常实例;throws是方法声明的一部分,用于声明可能抛出的异常类型。

throw 是语句,throws 是方法签名的一部分
throw 用来在代码里主动抛出一个异常对象,必须跟具体的异常实例;throws 则写在方法声明后面,只告诉调用者“这个方法可能会甩出哪些异常”,不抛具体对象。
常见错误现象:throw 后面写了类名(比如 throw IllegalArgumentException;)——会编译报错,因为 throw 要求是 Throwable 实例,不是类型。
-
throw只能出现在方法体内部,且只能抛一次(单条语句) -
throws可以声明多个异常,用逗号分隔,比如void read() throws IOException, SQLException - 如果方法里用了
throw抛出的是受检异常(checked exception),那它所在的函数必须有throws声明,或者用try-catch捕获
什么时候必须用 throws,什么时候可以用 throw
受检异常(如 IOException、SQLException)不能“偷偷抛”,JVM 强制你暴露风险:要么在当前方法用 try-catch 吞掉,要么用 throws 推给上层。非受检异常(RuntimeException 及其子类)则自由得多,throw 直接扔就行,throws 写不写都行(写了也不强制调用方处理)。
使用场景:
立即学习“Java免费学习笔记(深入)”;
- 校验失败时提前终止流程,用
throw new IllegalArgumentException("id must be positive") - 封装底层 IO 操作的工具方法,不自己处理文件读取失败,而是声明
throws IOException - API 接口层统一拦截
RuntimeException做日志和响应包装,内部大量用throw,但 service 方法签名几乎不写throws
throws 声明太多异常,调用方会很痛苦
如果一个方法签名叫 process() throws IOException, SQLException, TimeoutException, InterruptedException, ClassNotFoundException,说明它职责过重,或异常处理策略混乱。这不是语法错误,但会让调用方被迫写一堆 catch 或一路 throws 上去,破坏可读性与维护性。
性能 / 兼容性影响很小(throws 是编译期契约,不参与运行时逻辑),但设计上容易埋坑:
- 不要为了“看起来全面”而把所有可能异常都列进
throws,尤其别把RuntimeException显式声明(除非有特殊文档意图) - 考虑用自定义异常聚合底层多种异常,比如封装成
DataAccessException,再在throws里只写这一个 - Java 7+ 支持多异常捕获(
catch (IOException | SQLException e)),但throws列表依然不能简写成throws IOException | SQLException—— 语法不支持
throw null 会发生什么
可以编译通过,但运行时一定触发 NullPointerException(JVM 自动抛的),而且堆栈信息指向 throw 那一行,不是你期望的业务异常。这是个隐蔽的坑,尤其在条件分支里误把变量当异常对象传进去时。
示例:
String msg = null; throw msg; // 编译通过,运行时报 NPE,不是你写的那个异常
- 永远确保
throw后面是一个非 null 的Throwable实例 - IDE 通常不会警告
throw null,靠人眼或静态检查工具(如 SpotBugs)发现 - 构造异常时建议带上下文信息,比如
throw new IllegalStateException("cache not initialized", cause)
真正难的不是记住语法区别,而是判断该由谁来决定“这个异常要不要立刻中断执行”——throw 是执行时决策,throws 是设计时契约。契约定得模糊,后面每层调用都在替前一层擦屁股。










