砍材农夫砍材农夫
  • 微信记账小程序
  • java
  • redis
  • mysql
  • 场景类
  • 框架类
  • vuepress搭建
  • hexo搭建
  • 云图
  • 常用工具

    • git
    • gradle
    • Zadig
    • it-tools
    • 开源推荐
    • curl
  • 大前端

    • nodejs
    • npm
    • webpack
    • 微信
    • 正则
    • uniapp
  • java

    • java基础
    • jdk体系
    • jvm
    • spring
    • spring_cloud
    • spring_boot
    • 分库分表
    • zookeeper
  • python

    • python基础
    • python高级
    • python框架
  • 算法

    • 算法
  • 网关

    • spring_cloud_gateway
    • openresty
  • 高可用

    • 秒杀
    • 分布式
    • 缓存一致
  • MQ

    • MQ
    • rabbitMQ
    • rocketMQ
    • kafka
  • 其它

    • 设计模式
    • 领域驱动(ddd)
  • 关系型数据库

    • mysql5.0
    • mysql8.0
  • 非关系型数据库

    • redis
    • mongoDB
  • 分布式/其他

    • ShardingSphere
    • 区块链
  • 向量数据库

    • M3E
    • OPEN AI
  • Jmeter
  • fiddler
  • wireshark
  • AI入门
  • AI大模型
  • AI插件
  • AI集成框架
  • 相关算法
  • AI训练师
  • 量化交易
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 相关运营
  • docker
  • k8s
  • devops
  • nginx
  • 元宇宙
  • 区块链
  • 物联网
  • linux
  • webrtc
  • web3.0
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 中考
  • 投资
  • 保险
  • 思
  • 微信记账小程序
  • java
  • redis
  • mysql
  • 场景类
  • 框架类
  • vuepress搭建
  • hexo搭建
  • 云图
  • 常用工具

    • git
    • gradle
    • Zadig
    • it-tools
    • 开源推荐
    • curl
  • 大前端

    • nodejs
    • npm
    • webpack
    • 微信
    • 正则
    • uniapp
  • java

    • java基础
    • jdk体系
    • jvm
    • spring
    • spring_cloud
    • spring_boot
    • 分库分表
    • zookeeper
  • python

    • python基础
    • python高级
    • python框架
  • 算法

    • 算法
  • 网关

    • spring_cloud_gateway
    • openresty
  • 高可用

    • 秒杀
    • 分布式
    • 缓存一致
  • MQ

    • MQ
    • rabbitMQ
    • rocketMQ
    • kafka
  • 其它

    • 设计模式
    • 领域驱动(ddd)
  • 关系型数据库

    • mysql5.0
    • mysql8.0
  • 非关系型数据库

    • redis
    • mongoDB
  • 分布式/其他

    • ShardingSphere
    • 区块链
  • 向量数据库

    • M3E
    • OPEN AI
  • Jmeter
  • fiddler
  • wireshark
  • AI入门
  • AI大模型
  • AI插件
  • AI集成框架
  • 相关算法
  • AI训练师
  • 量化交易
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 相关运营
  • docker
  • k8s
  • devops
  • nginx
  • 元宇宙
  • 区块链
  • 物联网
  • linux
  • webrtc
  • web3.0
  • gitee
  • github
  • infoq
  • osc
  • 砍材工具
  • 关于
  • 中考
  • 投资
  • 保险
  • 思
  • 首页
  • 基础

    • map循环
    • 委托
    • 泛型
    • 线程池
    • String 为什么不可变?面试必问
    • Java 异常处理最佳实践,别再乱用 try-catch
    • 为什么禁止在for循环里使用+拼接字符串
    • HashMap底层原理面试必背精简版
    • HashSet、LinkedHashSet、TreeSet 区别与使用场景
    • Java创建线程的3种方式简单易懂
    • 如何使用 jstack 排查死锁
  • Java 异常处理最佳实践,别再乱用 try-catch
    • 1. 理解异常体系,用对类型
    • 2. 尽早抛出,晚点捕获(Throw Early, Catch Late)
    • 3. 不要吞掉异常(Never Swallow Exception)
    • 4. 使用 try-with-resources 管理资源
    • 5. 异常链与上下文信息
    • 6. 区分业务异常与系统异常
    • 7. 不要用异常控制业务流程
    • 8. 合理定义自定义异常
    • 9. 日志记录的原则
    • 10. 单元测试覆盖异常场景
    • 总结:异常处理黄金法则

Java 异常处理最佳实践,别再乱用 try-catch

在实际开发中,异常处理往往是被忽视但又极其重要的一环。滥用 try-catch 不仅会让代码变得臃肿难读,还可能掩盖潜在的问题。下面总结一套 Java 异常处理最佳实践,帮助你写出更优雅、更健壮的代码。


1. 理解异常体系,用对类型

Java 的异常分为三类:

  • Checked Exception(受检异常):如 IOException、SQLException,必须显式处理(try-catch 或 throws)。通常表示可恢复的外部错误。
  • Unchecked Exception(非受检异常):如 NullPointerException、IllegalArgumentException,是 RuntimeException 的子类,不强制处理。通常表示程序缺陷,不应捕获后继续执行。
  • Error:如 OutOfMemoryError,是 JVM 内部错误,应用层几乎不应该捕获。

✅ 最佳实践:

  • 对 Checked Exception 谨慎处理 —— 能恢复就处理,不能恢复就封装后抛出。
  • 对 RuntimeException 不要主动捕获,除非你能真正处理(如重试、降级)。
  • 永远不要捕获 Error。
// ❌ 错误示例
try {
    // 业务代码
} catch (Exception e) {  // 吞掉所有异常
    // 空处理
}

2. 尽早抛出,晚点捕获(Throw Early, Catch Late)

  • Throw Early:参数校验、状态检查应尽早进行,快速失败(fail-fast)。
  • Catch Late:只有在有明确处理逻辑(如回滚、重试、转换异常)时才捕获,否则让异常向上抛出。
// ✅ 示例:参数校验提前抛出
public void transfer(Account from, Account to, BigDecimal amount) {
    if (from == null || to == null || amount == null) {
        throw new IllegalArgumentException("参数不能为空");
    }
    if (amount.compareTo(BigDecimal.ZERO) <= 0) {
        throw new IllegalArgumentException("金额必须大于0");
    }
    // 业务逻辑
}

3. 不要吞掉异常(Never Swallow Exception)

捕获异常后什么都不做,是最危险的行为。

// ❌ 致命错误
try {
    // 业务代码
} catch (IOException e) {
    // 吞掉异常
}

✅ 正确处理方式:

  • 记录日志
  • 重新抛出业务异常
  • 返回明确的错误码/响应
try {
    // 业务代码
} catch (IOException e) {
    log.error("文件读取失败,文件路径: {}", path, e);
    throw new BusinessException("文件处理失败", e);
}

4. 使用 try-with-resources 管理资源

对于实现了 AutoCloseable 的资源(如流、连接),优先使用 try-with-resources,它会自动关闭资源,避免资源泄漏。

// ✅ 推荐
try (FileInputStream fis = new FileInputStream("file.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    // 读取文件
} catch (IOException e) {
    log.error("读取文件失败", e);
}

避免手动在 finally 中关闭,容易遗漏或出错。


5. 异常链与上下文信息

捕获异常后重新抛出时,不要丢失原始异常,使用 initCause 或构造函数传递 cause。

// ❌ 丢失原始异常
try {
    // 数据库操作
} catch (SQLException e) {
    throw new BusinessException("数据库错误");
}

// ✅ 保留原始异常
try {
    // 数据库操作
} catch (SQLException e) {
    throw new BusinessException("数据库错误", e);
}

同时,异常信息中应包含足够的上下文(如 ID、参数值),方便定位问题。

throw new BusinessException(String.format("用户[%s]转账失败,金额: %s", userId, amount), e);

6. 区分业务异常与系统异常

  • 业务异常:如余额不足、用户不存在,应定义为受检异常或自定义 RuntimeException,由业务层处理并返回友好提示。
  • 系统异常:如数据库连接失败、第三方接口超时,应在底层封装后抛出,由统一异常处理机制捕获并返回统一错误响应。

✅ 推荐:使用统一异常处理器(如 Spring 的 @RestControllerAdvice)统一处理异常,避免到处 try-catch。


7. 不要用异常控制业务流程

异常处理的性能开销较大,且会破坏代码可读性。

// ❌ 用异常控制流程
try {
    userService.findUser(id);
} catch (UserNotFoundException e) {
    // 新建用户
}

// ✅ 改用条件判断
Optional<User> userOpt = userService.findUser(id);
if (userOpt.isEmpty()) {
    // 新建用户
}

8. 合理定义自定义异常

  • 自定义异常应继承合适的父类(通常为 RuntimeException,除非强制调用方处理)。
  • 不要为了“省事”定义一个 BaseException 然后到处使用,应按场景细分。
// ✅ 示例
public class BusinessException extends RuntimeException {
    private final String errorCode;

    public BusinessException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public BusinessException(String errorCode, String message, Throwable cause) {
        super(message, cause);
        this.errorCode = errorCode;
    }
}

9. 日志记录的原则

  • 在异常被捕获并处理的地方记录日志,避免重复记录。
  • 使用 log.error 时带上异常对象,否则只会记录 message,丢失堆栈。
// ❌ 丢失堆栈
log.error("处理失败:" + e.getMessage());

// ✅ 打印完整堆栈
log.error("处理失败", e);

10. 单元测试覆盖异常场景

确保代码中的异常分支被测试覆盖,尤其是自定义异常和资源关闭逻辑。

@Test
void testTransfer_InsufficientBalance() {
    assertThrows(BusinessException.class, () -> {
        accountService.transfer(from, to, amount);
    });
}

总结:异常处理黄金法则

原则说明
明确异常类型Checked / Unchecked 各司其职
尽早抛出参数校验前置
晚点捕获只在有能力处理的地方捕获
不吞异常要么记录,要么抛出
资源自动关闭使用 try-with-resources
保留原始异常传递 cause
丰富上下文异常信息包含关键参数
统一处理全局异常处理器
不用于流程控制性能差,可读性差
记录日志要完整带上堆栈信息

记住:异常是代码的“故障说明书”,而不是“兜底毯子”。 优雅的异常处理,能让你的系统在出错时依然可控、可追溯、可恢复。

最近更新: 2026/3/27 16:28
Contributors: kcnf
Prev
String 为什么不可变?面试必问
Next
为什么禁止在for循环里使用+拼接字符串