核心区别(一句话总结)
throws:用在方法签名上,表示该方法可能会抛出某个异常,但不处理它,而是将处理责任抛给它的调用者。throw:用在方法体内部,是手动创建并抛出一个异常实例的动作。
throws 关键字
throws 的核心作用是声明,它告诉调用者:“我这个方法可能会出问题,可能会抛出异常,但我不想在这里处理,所以你得准备好处理它。”

语法
修饰符 返回值类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
// 方法体
}
特点
- 位置:必须跟在方法签名的参数列表之后。
- 作用:声明一个方法可能抛出的受检异常(Checked Exception),对于非受检异常(Unchecked Exception,即
RuntimeException及其子类),编译器不强制要求使用throws声明,但也可以使用。 - 责任转移:它将处理异常的责任从当前方法转移到了方法的调用者身上。
- 编译器检查:如果一个方法声明了受检异常,那么调用这个方法的代码必须处理这个异常(要么用
try-catch捕获,要么在自己的方法签名上继续使用throws声明)。
示例
假设我们有一个方法,它从一个文件中读取内容,由于文件可能不存在,FileInputStream 的构造函数会抛出 FileNotFoundException,这是一个受检异常。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowsExample {
// 声明这个方法可能会抛出 FileNotFoundException
// 它不处理这个异常,而是交给调用者处理
public void readFile(String filePath) throws FileNotFoundException {
System.out.println("准备读取文件: " + filePath);
FileInputStream fis = new FileInputStream(filePath); // 这行代码可能抛出 FileNotFoundException
System.out.println("文件打开成功!");
}
public static void main(String[] args) {
ThrowsExample example = new ThrowsExample();
// 调用者必须处理 readFile() 方法声明的异常
try {
example.readFile("non_existent_file.txt");
} catch (FileNotFoundException e) {
// 调用者捕获并处理了异常
System.err.println("捕获到异常:文件未找到!");
e.printStackTrace(); // 打印异常堆栈信息
}
System.out.println("程序继续执行...");
}
}
在这个例子中:
readFile方法使用throws FileNotFoundException声明了它可能抛出的异常。main方法作为readFile的调用者,必须用try-catch块来捕获这个异常,否则编译器会报错。
throw 关键字
throw 的核心作用是执行,它允许程序员在代码的任何地方手动抛出一个异常对象。
语法
throw new 异常类名(参数);
特点
- 位置:必须在方法体内部使用。
- 作用:主动抛出一个异常实例,这个异常可以是 Java 内置的,也可以是自定义的。
- 创建异常对象:
throw关键字后面必须跟一个异常对象(new Exception(...)),每次执行throw语句,都会创建一个新的异常对象,并立即中断当前方法的正常执行流程,然后沿着调用栈向上寻找匹配的catch块。 - 抛出何种异常:
- 你可以抛出任何
Throwable类或其子类的对象。 - 我们抛出受检异常(
Exception的子类)或非受检异常(RuntimeException的子类)。
- 你可以抛出任何
示例
假设我们有一个方法,用于设置一个人的年龄,年龄不能为负数,如果传入负数,我们就认为这是一个非法的参数,应该抛出异常。

public class ThrowExample {
// 自定义一个异常类,可选,但推荐
static class InvalidAgeException extends Exception {
public InvalidAgeException(String message) {
super(message);
}
}
// 手动抛出异常
public void setAge(int age) throws InvalidAgeException {
if (age < 0) {
// 创建一个异常对象并手动抛出
// 这会中断 setAge 方法的执行
throw new InvalidAgeException("年龄不能为负数!你输入的是: " + age);
}
System.out.println("年龄设置成功: " + age);
}
public static void main(String[] args) {
ThrowExample example = new ThrowExample();
try {
example.setAge(25); // 正常情况
example.setAge(-5); // 会抛出异常
} catch (InvalidAgeException e) {
// 捕获并处理我们手动抛出的异常
System.err.println("捕获到自定义异常:" + e.getMessage());
e.printStackTrace();
}
System.out.println("程序继续执行...");
}
}
在这个例子中:
setAge方法内部有一个逻辑判断,当条件不满足时,使用throw new InvalidAgeException(...)手动创建并抛出了一个异常。- 抛出异常后,
setAge方法中throw语句之后的代码(System.out.println...)将不会被执行。 main方法通过try-catch捕获了这个异常。
throws 与 throw 的关系与对比
为了更清晰地理解,我们用一个表格来对比它们:
| 特性 | throws |
throw |
|---|---|---|
throws |
throw |
|
| 语法 | throws ExceptionType |
throw new ExceptionType() |
| 作用 | 声明一个方法可能抛出的异常 | 执行一个操作,抛出一个异常实例 |
| 位置 | 方法签名之后 | 方法体内部 |
| 目的 | 告知调用者此方法可能产生的异常,将处理责任上交 | 在程序逻辑中主动中断流程,报告一个错误情况 |
| 与异常的关系 | 它不创建异常对象,只是一个声明 | 它必须创建一个异常对象并抛出 |
| 后续操作 | 调用者必须处理该异常(try-catch 或继续 throws) |
抛出后,当前方法立即终止,寻找 catch 块 |
何时使用?
使用 throws 的情况:
- 处理受检异常:当你的方法内部调用了另一个可能抛出受检异常的方法,而你不想在当前方法中处理它时,就可以在方法签名上使用
throws。 - 异常传递:当你希望异常能够沿着调用栈向上传递,由更高层的调用者(如
main方法或控制器层)来统一处理时。 - 框架设计:在设计 API 或框架时,有时希望将异常处理的策略留给 API 的使用者来决定。
使用 throw 的情况:
- 验证业务逻辑:当方法的输入参数不满足业务规则时(如年龄为负、用户名为空、密码太短等),可以抛出
IllegalArgumentException或自定义异常。 - 主动报告错误:在程序运行过程中,当检测到一个无法继续执行的状态时(如从缓存中获取的数据格式错误),可以手动抛出异常来中断流程。
- 实现自定义异常:当你需要抛出非标准的、特定于业务领域的错误时,会先创建一个自定义异常类,然后使用
throw来抛出它。
最佳实践
- 具体化异常:尽量抛出具体的异常类型,而不是笼统的
Exception,抛出FileNotFoundException比抛出IOException更能清晰地表达问题。 - 文档化异常:在 JavaDoc 中使用
@throws标签来记录方法可能抛出的异常,这能帮助其他开发者更好地使用你的代码。 - 不要抑制异常:避免使用空的
catch块(catch (Exception e) {}),这会隐藏错误信息,使得调试变得非常困难,至少要打印日志或记录异常信息。 - 优先使用非受检异常:对于程序内部的逻辑错误(如
NullPointerException,IllegalArgumentException),通常使用RuntimeException的子类,因为强制调用者处理这些本应由程序员修复的错误,会增加不必要的代码负担。throws主要用于处理那些真正由外部环境(如文件、网络)引起的、调用者可能需要做出相应决策的受检异常。
记住这个简单的比喻:
throws就像一个“免责声明”或“风险提示”,一个方法(比如蹦极项目)对你说:“这个项目有风险(可能会抛出异常),我们不负责,你自己决定要不要玩(调用者负责处理)。”throw就像是你自己主动拉下弹射绳的动作,你检查了装备(业务逻辑),发现不安全,于是你主动拉下绳索(抛出异常),中断了整个活动(方法执行)。
理解了这两者的区别,你就能在 Java 编写更健壮、更清晰的代码了。

