Java异常捕获案例如何精准处理

wen java案例 22

本文目录导读:

Java异常捕获案例如何精准处理

  1. 案例 1:避免“万能捕获”(最基础的精准)
  2. 案例 2:资源清理中的精准异常(IO/NIO 操作)
  3. 案例 3:数据库操作中的精准异常处理(JDBC)
  4. 案例 4:自定义异常与精准的业务逻辑
  5. 案例 5:异常处理中的“精准忽略”(anti-pattern 的反面)
  6. 总结:精准异常处理的 5 条黄金法则

在Java中,精准处理异常的核心原则是:捕获具体的异常类型,而不是笼统的 Exception,这能让你的代码更具可读性、更健壮,并能针对不同的错误采取不同的恢复策略。

下面通过几个案例,从简单到复杂,展示如何“精准处理”。

案例 1:避免“万能捕获”(最基础的精准)

错误做法(不精准):

try {
    String input = getUserInput(); // 可能返回 null
    int number = Integer.parseInt(input); // 可能抛出 NumberFormatException
    System.out.println(100 / number); // 可能抛出 ArithmeticException
} catch (Exception e) {
    // 不管是什么异常,都打印并吞掉(最糟糕的情况)
    System.out.println("出错了:" + e.getMessage());
}
// 问题:你不知道是空指针、数字格式错误还是除以0,无法针对性处理。

精准做法:

try {
    String input = getUserInput(); 
    int number = Integer.parseInt(input);
    System.out.println(100 / number);
} catch (NumberFormatException e) {
    // 精准处理:用户输入的不是数字
    System.out.println("输入格式错误,请输入一个整数: " + e.getMessage());
    // 可以提示用户重新输入
} catch (ArithmeticException e) {
    // 精准处理:算术运算异常(如除以0)
    System.out.println("除数不能为0,请重新输入: " + e.getMessage());
} catch (NullPointerException e) {
    // 精准处理:输入为空
    System.out.println("输入不能为空");
}

每个 catch 块只处理一种类型的异常,逻辑清晰。


案例 2:资源清理中的精准异常(IO/NIO 操作)

文件操作中,try-with-resources 已经帮我们解决了关闭资源的问题,但异常处理仍然需要精准。

场景: 读取配置文件,如果文件不存在,则使用默认配置;如果格式错误,则报错。

public static Properties loadConfig(String filePath) {
    Properties config = new Properties();
    try (InputStream input = new FileInputStream(filePath)) {
        config.load(input);
        System.out.println("成功加载配置文件");
    } catch (FileNotFoundException e) {
        // 精准处理:文件不存在 -> 使用默认值,不崩溃
        System.out.println("配置文件 " + filePath + " 未找到,使用默认配置");
        // 可以在这里设置默认值:config.setProperty("key", "defaultValue");
    } catch (IOException e) {
        // 精准处理:读取过程中发生IO错误 -> 需要向上抛或记录严重日志
        System.err.println("读取配置文件时发生IO错误: " + e.getMessage());
        // 这里可以抛出一个自定义运行时异常,让上层决定如何处理
        throw new RuntimeException("无法读取配置文件", e);
    }
    return config;
}

关键点: FileNotFoundExceptionIOException 的子类。一定要先捕获具体的子类,再捕获父类,否则子类的 catch 块永远不会被执行。


案例 3:数据库操作中的精准异常处理(JDBC)

数据库操作涉及连接、SQL语法、数据约束等多种异常。

场景: 用户注册,插入用户记录。

public void registerUser(User user) {
    String sql = "INSERT INTO users (username, email) VALUES (?, ?)";
    try (Connection conn = getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setString(1, user.getUsername());
        pstmt.setString(2, user.getEmail());
        pstmt.executeUpdate();
        System.out.println("用户注册成功");
    } catch (SQLIntegrityConstraintViolationException e) {
        // 精准处理:唯一约束冲突(如用户名或邮箱已存在)
        // 这是业务逻辑层面的错误,需要给用户友好的提示
        System.out.println("注册失败:用户名或邮箱已被注册");
        // 不要将此异常继续向上抛,因为这是预期的业务错误
    } catch (SQLSyntaxErrorException e) {
        // 精准处理:SQL语法错误(通常是开发Bug)
        System.err.println("数据库查询错误(SQL语法问题):" + e.getMessage());
        // 应该立即通知开发人员修复
    } catch (SQLTransientConnectionException e) {
        // 精准处理:数据库连接失败(可重试的临时问题)
        System.out.println("数据库暂时不可用,请稍后再试");
        // 可以增加重试逻辑
    } catch (SQLException e) {
        // 兜底处理:其他未预料到的数据库异常
        System.err.println("未知数据库错误:" + e.getMessage());
        // 记录详细日志,避免干扰用户
    }
}

技巧: 利用 SQLException 的子类(在 java.sqlcom.mysql.cj.jdbc.exceptions 等驱动包中)来区分不同类型的数据库错误。


案例 4:自定义异常与精准的业务逻辑

当系统异常无法准确描述业务意图时,自定义异常是精准处理的最佳工具。

场景: 银行转账,余额不足、账户冻结、超过日限额。

// 自定义异常类
class InsufficientBalanceException extends RuntimeException {
    private double currentBalance;
    private double requiredAmount;
    public InsufficientBalanceException(double currentBalance, double requiredAmount) {
        super("余额不足,当前余额: " + currentBalance + ", 需要: " + requiredAmount);
        this.currentBalance = currentBalance;
        this.requiredAmount = requiredAmount;
    }
    // getter...
}
class AccountFrozenException extends Exception { // 可以是检查型异常
    public AccountFrozenException(String accountId) {
        super("账户 " + accountId + " 已被冻结");
    }
}
// 业务处理器
public void transfer(String fromAccount, String toAccount, double amount) 
        throws AccountFrozenException {  // 声明检查型异常
    // 1. 检查账户状态
    if (isFrozen(fromAccount)) {
        throw new AccountFrozenException(fromAccount);
    }
    // 2. 检查余额
    double balance = getBalance(fromAccount);
    if (balance < amount) {
        // 精准异常:携带了具体的错误数据,方便上层展示
        throw new InsufficientBalanceException(balance, amount);
    }
    // 3. 执行转账(省略)
    System.out.println("转账成功");
}
// 调用处(精准捕获)
try {
    transfer("123", "456", 5000);
} catch (InsufficientBalanceException e) {
    // 精准提示:告诉用户还差多少钱
    System.out.println("转账失败:" + e.getMessage() + 
                       ",您还缺 " + (e.getRequiredAmount() - e.getCurrentBalance()) + " 元");
} catch (AccountFrozenException e) {
    System.out.println("账户异常:" + e.getMessage());
} catch (Exception e) {
    // 其他技术异常(如网络中断)
    System.err.println("系统繁忙,请稍后重试");
}

好处: 自定义异常携带了准确的业务数据(如余额、冻结原因),让 catch 块能做出最合适的响应。


案例 5:异常处理中的“精准忽略”(anti-pattern 的反面)

某些异常确实可以安全忽略,但必须显式注明原因

// 精准忽略:明确知道该异常在此场景下无害,并记录一下
try {
    connection.setAutoCommit(false);
} catch (SQLException e) {
    // 此数据库驱动可能不支持自动提交设置,忽略此异常,因为后续代码会处理事务
    // 但为了调试需要,记录一下日志
    logger.debug("不支持设置 autoCommit false,继续执行: {}", e.getMessage());
}
// 注意:不要空 catch 块,注释必须明确说明为什么忽略。

精准异常处理的 5 条黄金法则

  1. 捕获最具体的类型catch (NumberFormatException e) 而不是 catch (Exception e)
  2. 多 catch 块顺序:子类在前,父类在后。
  3. 使用 try-with-resources:自动关闭资源,并支持 addSuppressed 异常,不会丢失原始异常。
  4. 区分“可恢复”和“不可恢复”
    • 可恢复(如用户输入错误、文件不存在):处理异常,给出友好提示,让程序继续运行。
    • 不可恢复(如网络断开、SQL语法错误):记录日志,向上抛(通常转为 RuntimeException),让全局错误处理器处理。
  5. 善用自定义异常:将技术异常(如 SQLException)转化为业务异常(如 AccountFrozenException),让调用代码更清晰。

永远不要这样写

try {
    // ...
} catch (Exception e) {
    e.printStackTrace(); // 或 logger.error(e.getMessage());
    // 没有恢复、没有转换、甚至没有日志级别区分
}

这个代码在线上等于“我不知道发生了什么,也不打算处理”,是线上故障的常见根源。

抱歉,评论功能暂时关闭!