如何优雅处理Java异常
在 Java 中,“优雅处理异常”的核心目标是:让程序健壮、可维护、可读,同时不掩盖问题、不滥用异常机制。下面从设计原则、实践技巧和常见误区三个层面系统讲解。
一、核心原则(先有认知)
1. 异常只用于“异常场景”
❌ 不要用异常做正常流程控制:
// 错误示例
try {
int value = map.get(key);
} catch (NullPointerException e) {
value = defaultValue;
}
✅ 正确做法:
Integer value = map.get(key);
if (value == null) {
value = defaultValue;
}
2. 区分“可恢复”和“不可恢复”异常
| 类型 | 示例 | 处理方式 |
|---|---|---|
| 可恢复 | 用户输入错误、网络重试 | catch 后处理 |
| 不可恢复 | 空指针、OOM、系统错误 | 抛出或记录后终止 |
二、异常设计层面(优雅的基础)
1. 自定义异常要有意义
不要一个项目只有 RuntimeException。
✅ 推荐:
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(String orderId) {
super("Order not found: " + orderId);
}
}
✅ 如果是可恢复异常:
public class PaymentFailedException extends Exception {}
2. 使用异常链(保留根因)
永远不要吃掉原始异常
❌ 错误:
catch (SQLException e) {
throw new RuntimeException("DB error");
}
✅ 正确:
catch (SQLException e) {
throw new RuntimeException("DB error", e);
}
三、代码层面的优雅实践
1. 精确捕获异常(不要 catch Exception)
❌ 不推荐:
try {
...
} catch (Exception e) {
e.printStackTrace();
}
✅ 推荐:
try {
...
} catch (IOException | SQLException e) {
log.error("业务失败", e);
}
2. 早 throw,晚 catch
- 在问题发生的地方抛
- 在能处理的地方 catch
public void processOrder(Order order) throws OrderInvalidException {
if (order == null) {
throw new OrderInvalidException("order is null");
}
}
try {
processOrder(order);
} catch (OrderInvalidException e) {
// 统一处理
}
3. 使用 finally / try-with-resources
✅ 自动关闭资源(强烈推荐):
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
return br.readLine();
}
❌ 不要手动关闭资源(容易漏)
4. 统一异常处理(Web 项目)
Spring Boot 示例
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
public Response handle(OrderNotFoundException e) {
return Response.error(404, e.getMessage());
}
@ExceptionHandler(Exception.class)
public Response handle(Exception e) {
log.error("系统异常", e);
return Response.error(500, "系统异常");
}
}
✅ 优点:
- 业务代码干净
- 异常集中管理
- 返回结构统一
四、日志与异常一起处理
1. 不要只打印,不记录
❌
e.printStackTrace();
✅
log.error("支付失败,orderId={}", orderId, e);
2. 不要重复记录异常
catch (Exception e) {
log.error("error", e);
throw e; // 上层还会再记一次
}
✅ 要么记录,要么抛出,不要都做。
五、常见反模式(一定要避免)
❌ 1. 空 catch
catch (Exception e) {
// ignore
}
❌ 2. 用异常做 if-else
try {
return list.get(0);
} catch (IndexOutOfBoundsException e) {
return null;
}
❌ 3. 捕获 Throwable
catch (Throwable t) { }
六、一个“优雅处理异常”的示例
public void createUser(User user) {
if (user == null) {
throw new IllegalArgumentException("user is null");
}
try {
userRepository.save(user);
} catch (DuplicateKeyException e) {
throw new BusinessException("用户已存在", e);
}
}
七、一句话总结
优雅处理 Java 异常 = 精准抛、合理捕、保留原因、统一处理、不滥用。
如果你愿意,我可以:
- 结合你当前项目(Spring / 普通 Java)给具体示例
- 对比 Checked vs Unchecked Exception 的最佳实践
- 帮你设计一套异常与返回码规范