Java 异常处理详解(2025–2026 现代实践版)
Java 异常处理是 Java 语言的核心机制之一,用于处理程序运行时的错误或异常情况,确保程序的健壮性和可维护性。异常处理可以避免程序崩溃、记录错误信息、恢复程序状态或优雅退出。
本文从基础概念 → 核心语法 → 进阶技巧 → 最佳实践 全流程详解,结合代码示例和生产经验。基于 Java 21+(LTS 版本),但兼容旧版。
1. 异常基础概念
- 什么是异常?
异常(Exception)是程序运行时发生的异常事件(如除零、网络断开、文件不存在),它中断了正常的执行流程。Java 将异常视为对象,所有异常都继承自Throwable类。 - 异常层次结构(关键类图):
| 顶级类 | 子类示例 | 描述 |
|---|---|---|
| Throwable | Error | 严重错误(如 OutOfMemoryError),不可恢复,不建议捕获 |
| Exception | 可恢复异常,分两类:Checked 和 Unchecked | |
| Exception | Checked Exception | 编译时检查,必须处理(如 IOException、SQLException) |
| Unchecked Exception | 运行时异常,无需强制处理(如 NullPointerException、ArrayIndexOutOfBoundsException),继承自 RuntimeException |
- Checked vs Unchecked(面试高频):
- Checked:必须在方法签名用
throws声明,或用 try-catch 处理。适合可预见、可恢复的异常(如文件读写)。 - Unchecked:无需声明,适合编程错误(如空指针)。生产中,Unchecked 异常往往表示 bug,应优先避免。
2. 核心语法:try-catch-finally
- 基本结构:
try {
// 可能抛出异常的代码
} catch (ExceptionType1 e) {
// 处理异常1
} catch (ExceptionType2 e) {
// 处理异常2(多 catch 块,按顺序匹配,先子类后父类)
} finally {
// 无论是否异常,都执行(资源释放神器,如关闭连接)
}
- 示例1:简单捕获(Unchecked 异常):
public class BasicException {
public static void main(String[] args) {
try {
int[] arr = new int[2];
System.out.println(arr[3]); // 抛出 ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组越界:" + e.getMessage());
} finally {
System.out.println("finally 总会执行");
}
}
}
输出:
数组越界:Index 3 out of bounds for length 2
finally 总会执行
- 示例2:多 catch + Checked 异常:
import java.io.FileReader;
import java.io.IOException;
public class CheckedExample {
public static void main(String[] args) {
try {
FileReader fr = new FileReader("nonexistent.txt"); // 抛出 FileNotFoundException
} catch (IOException e) { // 父类捕获
System.out.println("IO 异常:" + e);
} catch (Exception e) { // 更广义捕获(放最后)
System.out.println("通用异常:" + e);
} finally {
System.out.println("清理资源");
}
}
}
- Java 7+ 新特性:try-with-resources(自动关闭资源):
try (FileReader fr = new FileReader("file.txt")) {
// 使用 fr
} catch (IOException e) {
// 处理
} // 无需 finally,fr 自动 close()
3. throw 和 throws
- throw:手动抛出异常对象。
- throws:方法签名声明可能抛出的 Checked 异常,交给调用者处理。
- 示例:
public class ThrowExample {
public static void checkAge(int age) throws IllegalArgumentException { // throws 声明
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负!"); // throw 抛出
}
System.out.println("年龄正常:" + age);
}
public static void main(String[] args) {
try {
checkAge(-1);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
输出:年龄不能为负!
4. 自定义异常
- 为什么自定义?标准异常无法表达业务语义时(如“余额不足”)。
- 步骤:继承 Exception(Checked)或 RuntimeException(Unchecked)。
- 示例(Unchecked 自定义):
// 自定义异常类
public class InsufficientBalanceException extends RuntimeException {
public InsufficientBalanceException(String message) {
super(message);
}
}
// 使用
public class BankAccount {
private double balance = 100;
public void withdraw(double amount) {
if (amount > balance) {
throw new InsufficientBalanceException("余额不足,仅剩 " + balance);
}
balance -= amount;
}
}
5. 进阶技巧
- 异常链(Exception Chaining):捕获低级异常,包装成高级异常抛出,保留原始原因。
try {
// 低级操作
} catch (LowLevelException e) {
throw new HighLevelException("高级错误", e); // 包装
}
- 好处:
e.getCause()可追溯根源。 - 异常抑制(Suppressed Exceptions):try-with-resources 中,如果 close() 也抛异常,用
e.getSuppressed()获取。 - 异步异常:多线程中,异常不会跨线程传播。需在 Runnable/Callable 中手动处理,或用 UncaughtExceptionHandler。
Thread.setDefaultUncaughtExceptionHandler((thread, e) -> {
System.out.println("线程异常:" + e);
});
- 性能影响:异常是昂贵的(栈追踪开销),避免用异常控制流程(如用 if 代替)。
6. 最佳实践(生产级建议)
- 捕获具体异常:优先捕获子类(如 IOException 而非 Exception),避免隐藏 bug。
- 不要吞异常:catch 块至少记录日志(如 log.error(e)),别空 catch。
- finally 释放资源:或优先 try-with-resources。
- Checked 异常 vs Unchecked:
- Checked:用于可恢复场景(如用户输入)。
- Unchecked:用于编程错误(如参数校验)。
- 日志记录:用 SLF4J/Log4j 记录栈追踪:
log.error("错误", e);。 - 全局异常处理(Spring 项目):用 @ControllerAdvice + @ExceptionHandler。
- 避免过度捕获:让异常向上传播到合适层级处理。
- 测试异常:用 JUnit @Test(expected = Exception.class) 或 Assertions.assertThrows()。
- 2025+ 趋势:结合 Virtual Thread(Project Loom),异常在并发中更易追踪。
常见问题排查
- StackOverflowError:递归太深或无限循环。
- OutOfMemoryError:内存泄漏,监控 JVM(用 jvisualvm)。
- NullPointerException:最常见,防御式编程(Optional 或 null 检查)。
掌握这些,你能写出更可靠的 Java 代码!如果想深入某个部分(如 Spring 异常处理或更多示例),告诉我,我们继续。