多个 catch 块 (Multiple Catch Blocks)
这是最传统、最直观的方式,为每个需要捕获的异常类型编写一个独立的 catch 块。

语法结构
try {
// 可能抛出异常的代码
int result = 10 / 0;
String str = null;
int length = str.length();
} catch (ArithmeticException e) {
// 专门处理算术异常,如除以零
System.err.println("捕获到算术异常: " + e.getMessage());
} catch (NullPointerException e) {
// 专门处理空指针异常
System.err.println("捕获到空指针异常: " + e.getMessage());
} catch (Exception e) {
// 这是一个通用的异常捕获,通常放在最后,用于捕获未预料到的其他异常
System.err.println("捕获到未知异常: " + e.getMessage());
}
工作原理
JVM 会按照 catch 块的书写顺序,从上到下依次检查抛出的异常对象是否与 catch 块中的异常类型匹配,一旦找到第一个匹配的 catch 块,就会执行该块中的代码,然后跳出整个 try-catch 结构。
重要提示:更具体的异常类型应该放在更通用的异常类型之前,如果将 catch (Exception e) 放在最前面,它将捕获所有类型的异常,导致后面的 catch 块永远无法被执行,这在编译时不会报错,但逻辑上是错误的。
// 错误的顺序!
catch (Exception e) { ... } // 这个会捕获所有异常
catch (ArithmeticException e) { ... } // 这行代码永远不会执行到
优点
- 清晰明了:每个异常都有自己独立的处理逻辑,代码意图非常清晰,易于阅读和维护。
- 处理方式灵活:可以为不同类型的异常提供完全不同的恢复策略或错误信息。
- 调试方便:在调试时,可以轻松定位到是哪个特定的异常分支被触发了。
缺点
- 代码冗余:如果多个异常的处理逻辑完全相同,就会导致重复的代码。
单个 catch 块捕获多个异常 (Java 7+)
从 Java 7 开始,引入了一种更简洁的语法,允许在一个 catch 块中捕获多个不同类型的异常,这些异常类型使用竖线 分隔。
语法结构
try {
// 可能抛出异常的代码
int result = 10 / 0;
String str = null;
int length = str.length();
} catch (ArithmeticException | NullPointerException e) {
// 一个 catch 块处理两种异常
System.err.println("捕获到算术异常或空指针异常: " + e.getMessage());
// 注意:这里的 e 变量是 final 的,不能被重新赋值
// e = new Exception(); // 编译错误!
}
工作原理
这种方式在底层会被编译器转换成多个 catch 块,编译器会为 分隔的每个异常类型都生成一个隐式的 catch 块,并将你的代码块内容复制到每个隐式 catch 块中。

// 编译器内部大致会转换成这样
try {
// ...
} catch (ArithmeticException e) {
System.err.println("捕获到算术异常或空指针异常: " + e.getMessage());
} catch (NullPointerException e) {
System.err.println("捕获到算术异常或空指针异常: " + e.getMessage());
}
优点
- 代码简洁:当多个异常的处理逻辑相同时,可以大大减少代码量,避免重复。
- DRY (Don't Repeat Yourself):遵循了不重复代码的原则。
缺点
- 处理逻辑统一:所有被捕获的异常都必须执行完全相同的处理逻辑,如果需要对不同异常进行不同处理,则无法使用此方式。
- 异常变量限制:在
catch块中,异常变量e被隐式声明为final,不能被重新赋值。 - 可能掩盖细节:将不同来源的异常用同一种方式处理,可能会掩盖某些异常特有的重要信息。
如何选择?最佳实践
选择哪种方式取决于你的具体需求。
使用 catch (Exception1 | Exception2 e) 当:
- 多个异常的处理逻辑完全相同,这是使用此语法最主要的原因。
- 你希望代码更简洁,减少冗余。
示例:
假设你正在关闭一个 AutoCloseable 资源(如 FileInputStream),close() 方法可能抛出 IOException,你的业务逻辑也可能抛出 SQLException,如果对这两个异常的处理都是简单地打印日志并记录,那么就可以合并。
try (FileInputStream fis = new FileInputStream("test.txt")) {
// 业务逻辑,可能抛出 SQLException
// ...
} catch (IOException | SQLException e) {
// 对两种异常执行相同的操作:记录日志
logger.error("操作失败", e);
}
使用多个 catch 块 当:
- 不同异常需要不同的处理逻辑,这是最常见的情况,也是推荐的做法。
- 需要针对特定异常类型提供更精确的错误信息或恢复机制。
- 你需要根据异常类型执行不同的代码分支。
示例:
用户登录时,可能因为密码错误(BadCredentialsException)或账户被锁定(AccountLockedException)而失败,这两种情况应该给用户不同的提示。
try {
user.login(username, password);
} catch (BadCredentialsException e) {
// 提示用户密码错误
showErrorMessage("用户名或密码错误,请重试。");
} catch (AccountLockedException e) {
// 提示用户账户被锁定
showErrorMessage("账户已被锁定,请联系管理员。");
} catch (LoginException e) {
// 其他登录异常
showErrorMessage("登录失败,请稍后再试。");
}
| 特性 | 多个 catch 块 | 单个 catch 块 () |
| :--- | :--- | :--- |
| 适用场景 | 异常处理逻辑不同 | 异常处理逻辑相同 |
| 代码清晰度 | 非常高,逻辑分离清晰 | 较高,但可能掩盖差异 |
| 代码简洁性 | 可能冗余 | 非常简洁,避免重复 |
| 灵活性 | 高,可为每个异常定制处理 | 低,必须统一处理 |
| Java 版本 | Java 1.0+ | Java 7+ |

核心建议:
优先使用多个 catch 块,因为它能让代码的意图更清晰。 只有当明确知道多个异常的处理逻辑完全相同时,才考虑使用 catch (Exception1 | Exception2 e) 来简化代码。
