多个 catch 块 (Multiple catch Blocks)
这是最传统、最直观的方式,针对不同的异常类型,分别使用 catch 块来处理。

语法结构
try {
// 可能抛出多种异常的代码
int result = 10 / 0; // 抛出 ArithmeticException
String str = null;
int length = str.length(); // 抛出 NullPointerException
} catch (ArithmeticException e) {
// 专门处理算术异常
System.err.println("发生算术异常: " + e.getMessage());
} catch (NullPointerException e) {
// 专门处理空指针异常
System.err.println("发生空指针异常: " + e.getMessage());
} catch (Exception e) {
// 捕获其他所有未捕获的异常(作为兜底)
System.err.println("发生未知异常: " + e.getMessage());
}
特点
- 独立处理:每个
catch块可以针对特定类型的异常进行独立的、差异化的处理逻辑。 - 顺序重要:
catch块的顺序必须遵循从具体到一般的规则,子类异常必须在其父类异常之前被捕获,否则,编译器会报错。- 错误示例:
catch (Exception e) { // 捕获所有异常 // ... } catch (IOException e) { // IOException 是 Exception 的子类,永远不会被执行到 // ... } // 编译错误: exception IOException has already been caught
- 错误示例:
- 清晰明了:代码结构清晰,易于阅读和维护,一眼就能看出每种异常的处理方式。
单个 catch 块捕获多个异常 (Java 7+ 引入)
从 Java 7 开始,引入了一种更简洁的语法,允许在一个 catch 块中捕获多个不同类型的异常,这些异常类型使用竖线 分隔。
语法结构
try {
// 可能抛出多种异常的代码
int result = 10 / 0; // 抛出 ArithmeticException
String str = null;
int length = str.length(); // 抛出 NullPointerException
} catch (ArithmeticException | NullPointerException e) {
// 使用同一个处理逻辑处理这两种异常
System.err.println("发生算术异常或空指针异常: " + e.getMessage());
}
特点
- 代码简洁:当多个异常的处理逻辑完全相同时,这种方式可以大大减少代码量,避免重复的
catch块。 - 异常参数为
final:在 Java 7 中,捕获多个异常的catch块中的异常参数e被隐式地声明为final,这意味着你不能在catch块中修改这个引用(e = new AnotherException())。- Java 9+ 的改进:从 Java 9 开始,这个
final限制被移除了,你可以在catch块中重新赋值给异常变量。
- Java 9+ 的改进:从 Java 9 开始,这个
- 异常类型检查:编译器会检查 分隔的异常类型之间是否存在继承关系,如果其中一个异常是另一个的子类,编译器会报错,因为这是冗余的(子类已经被父类覆盖)。
- 错误示例:
// IOException 是 Exception 的子类,这样写是冗余的 catch (IOException | Exception e) { // ... } // 编译错误: The exception IOException is already caught by the alternative Exception
- 错误示例:
两种方式的对比与选择
| 特性 | 多个 catch 块 |
单个 catch 块 (Java 7+) |
|---|---|---|
| 适用场景 | 不同异常需要不同处理逻辑时 | 多个异常需要相同处理逻辑时 |
| 代码可读性 | 非常清晰,逻辑分离 | 代码更紧凑,减少了重复 |
| 处理灵活性 | 高,可以为每种异常定制处理方式 | 较低,所有异常共享同一段处理逻辑 |
异常参数 final |
无此限制 | Java 7 中为 final,Java 9+ 中已移除 |
| 最佳实践 | 推荐,特别是当异常处理逻辑有差异时 | 推荐,当处理逻辑完全相同时,能简化代码 |
最佳实践与建议
-
优先使用“具体”的异常:尽量捕获具体的异常类型(如
IOException),而不是笼统的Exception或RuntimeException,这样可以更精确地定位和解决问题,避免意外掩盖了其他未预料到的错误。 -
处理逻辑相同时,使用 合并:如果两个或多个异常的处理方式完全一样,使用 符号将它们合并到一个
catch块中,这是 Java 7+ 的一个优秀特性,能让代码更简洁。 -
不要“吞噬”异常:在
catch块中,至少应该记录日志(e.printStackTrace()或使用日志框架如 SLF4J/Log4j),或者将异常重新抛出(throw new MyCustomException("描述", e);),仅仅catch然后什么都不做(空catch块)是极其糟糕的做法,这会给后续的调试和维护带来巨大的困难。
(图片来源网络,侵删) -
合理利用异常层次结构:如果一个
try块可能抛出多个相关的异常(都继承自同一个父类),你可以先捕获子类进行特殊处理,然后用一个catch块捕获父类进行通用处理。
综合示例
import java.io.IOException;
import java.sql.SQLException;
public class CatchMultipleExceptionsExample {
public void processData() {
try {
// 模拟可能抛出不同异常的操作
// someOperationThatMightThrowIO();
// someDatabaseOperationThatMightThrowSQL();
throw new IOException("文件读取失败");
// throw new SQLException("数据库连接失败");
// throw new NullPointerException("数据为空");
} catch (IOException | SQLException e) {
// 处理所有与 I/O 和数据库相关的异常
// 假设它们的处理逻辑都是记录日志并重试
System.err.println("资源访问失败,准备重试: " + e.getMessage());
// log.error("资源访问失败", e);
// retryOperation();
} catch (NullPointerException e) {
// 空指针异常需要特殊处理,比如提示用户
System.err.println("数据错误,请联系管理员: " + e.getMessage());
// log.error("关键数据为空", e);
// notifyUser("系统数据异常,请联系管理员");
} catch (Exception e) {
// 兜底,捕获所有其他未预料到的严重异常
System.err.println("发生严重未知错误,系统将关闭: " + e.getMessage());
// log.error("系统发生严重未知错误", e);
// System.exit(1);
}
}
public static void main(String[] args) {
new CatchMultipleExceptionsExample().processData();
}
}
- 多种异常,多种处理 -> 使用多个
catch块。 - 多种异常,一种处理 -> 使用 Java 7+ 的单个
catch块 + 。
在现代 Java 开发中,你应该根据具体的业务场景和需求,灵活地选择这两种方式,以写出既健壮又易于维护的代码。

