异常处理最佳实践
异常处理看起来简单,但里面全是坑。这篇文章帮你把这些坑一个个踩平。
三种异常类型
Throwable
├── Error (系统错误,程序无法处理)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception (可处理的异常)
├── RuntimeException (运行时异常)
│ ├── NullPointerException
│ ├── IndexOutOfBoundsException
│ ├── IllegalArgumentException
│ └── ClassCastException
└── 受检异常 (Checked Exception)
├── IOException
├── SQLException
└── FileNotFoundExceptiontry-catch-finally 执行顺序
java
try {
System.out.println("try 块");
} catch (Exception e) {
System.out.println("catch 块");
} finally {
System.out.println("finally 块");
}
// 无异常:try → finally
// 有异常:try → catch → finallyfinally 不执行只有一种情况:System.exit() 终止了 JVM。
避坑指南
不要捕获 Throwable
java
// ❌ 捕获所有异常——Error 不该被捕获
try {
// ...
} catch (Throwable t) {
// Error 不应该捕获
}
// ✅ 只捕获预期的异常
try {
// ...
} catch (IOException e) {
// 处理 IOException
}先捕获子类后捕获父类
java
try {
// ...
} catch (FileNotFoundException e) {
// 先捕获具体的异常
} catch (IOException e) {
// 后捕获父类异常
}不要吞掉异常
java
// ❌ 吞掉异常——出了 bug 都不知道在哪
try {
// ...
} catch (Exception e) {
// 什么都不做
}
// ✅ 至少记录日志
try {
// ...
} catch (Exception e) {
log.error("操作失败", e);
throw e;
}不要用异常控制流程
java
// ❌ 用异常做流程控制——慢、丑、容易出错
for (int i = 0; i < 1000; i++) {
try {
Integer.parseInt(someString);
} catch (NumberFormatException e) {
// 处理
}
}
// ✅ 先验证再执行
if (isValidNumber(someString)) {
Integer.parseInt(someString);
}最佳实践
使用 try-with-resources
java
// ❌ 传统方式容易漏 close
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("file.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
log.error("读取文件失败", e);
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
log.error("关闭流失败", e);
}
}
}
// ✅ try-with-resources 自动关闭
try (BufferedReader br = new BufferedReader(
new FileReader("file.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
log.error("读取文件失败", e);
}包装异常
java
// ❌ 直接抛出原始异常暴露实现细节
try {
dao.save(user);
} catch (SQLException e) {
throw e;
}
// ✅ 包装为业务异常
try {
dao.save(user);
} catch (SQLException e) {
throw new ServiceException("保存用户失败", e);
}提供有意义的异常信息
java
// ❌ 空异常——让人摸不着头脑
throw new IllegalArgumentException();
// ✅ 附带上下文
throw new IllegalArgumentException(
"Invalid user id: " + userId + ". User id must be positive.");合理使用断言
java
// ❌ 运行时检查可能忘记调用
if (object == null) {
throw new IllegalArgumentException();
}
// ✅ 断言用于开发测试阶段检查
assert object != null : "object must not be null";异常处理策略
| 策略 | 说明 | 使用场景 |
|---|---|---|
| 记录并重新抛出 | 记录日志后重新抛出 | 异常需要上层处理 |
| 包装异常 | 包装为业务异常 | 隐藏实现细节 |
| 恢复 | 尝试恢复执行 | 可恢复的错误 |
| 降级 | 提供默认值 | 非关键功能 |
自定义业务异常
java
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String message) {
super(message);
this.code = "BUSINESS_ERROR";
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public String getCode() {
return code;
}
}java
public void withdraw(Account account, double amount) {
if (amount <= 0) {
throw new BusinessException("INVALID_AMOUNT", "金额必须大于0");
}
if (account.getBalance() < amount) {
throw new BusinessException("INSUFFICIENT_BALANCE", "余额不足");
}
// ...
}