Java 异常与错误处理

Java 异常体系、受检/非受检异常、抛出与捕获边界:失败如何成为可设计的控制流。

#type / concept #status / growing #tech / dev #resource / java

[!info] 关联笔记

Java 异常与错误处理

这个概念为什么出现

IO 失败、参数非法、下游超时都是常态。Java 用异常对象 + 栈展开表达失败,并区分受检异常(checked)与非受检异常(unchecked)。不会设计异常边界,就会出现要么“吞异常”,要么“到处 throws Exception”。

[!abstract] 一句话理解 异常是带类型与栈轨迹的失败信号;Error/RuntimeException 非受检,其它 Exception 子类默认受检;在边界捕获、转译、记录,在内核保持不变量。

最小可运行示例

场景:库存预留——参数错误与“库存不足”要用不同异常策略

class InsufficientStockException extends Exception {
    InsufficientStockException(String msg) { super(msg); }
}

public class StockService {
    private int stock;
    StockService(int stock) { this.stock = stock; }

    // reserve:受检异常表达可预期业务失败(示例风格;团队也可选非受检)
    // 教学点:非法参数用非受检;业务可恢复失败可用受检或领域异常策略。
    int reserve(int qty) throws InsufficientStockException {
        if (qty <= 0) {
            throw new IllegalArgumentException("qty must be > 0");
        }
        if (qty > stock) {
            throw new InsufficientStockException("need=" + qty + " stock=" + stock);
        }
        stock -= qty;
        return stock;
    }

    public static void main(String[] args) {
        StockService svc = new StockService(5);
        try {
            System.out.println("left=" + svc.reserve(3));
            System.out.println("left=" + svc.reserve(10));
        } catch (InsufficientStockException ex) {
            System.out.println("biz-fail: " + ex.getMessage());
        } catch (IllegalArgumentException ex) {
            System.out.println("bad-arg: " + ex.getMessage());
        }
    }
}

建议运行:

javac StockService.java && java StockService

期望输出:

left=2
biz-fail: need=10 stock=2

结合场景再看三个关注点

  1. 异常类型本身就是 API,调用方靠类型分支处理。
  2. 不要 catch Exception 后空着——至少记录或转译。
  3. 受检异常强迫声明;滥用会污染签名,需要分层转译。

核心概念与准确模型

1. 层次(简化)

Throwable
├── Error          // 严重,通常不捕获恢复
└── Exception
    ├── RuntimeException  // 非受检
    └── 其它 Exception    // 受检

2. 关键词

  • throw 抛出
  • throws 声明
  • try/catch/finally 捕获与清理(资源优先 try-with-resources)

3. 设计问题

问题建议
编程错误(NPE、越界)非受检,修代码
可预期外部失败受检或明确领域非受检 + 文档
跨层转译为调用方能理解的类型
日志在边界记一次,避免每层重复刷屏

4. 异常链

throw new IllegalStateException("pay failed", cause);

保留 cause 便于排障。

边界情况与反直觉行为

  1. finally 中 return 会掩盖 try 中异常/返回值——禁止。
  2. 捕获后重新抛要保留栈或正确包装。
  3. 异步线程中的异常不会自动回到提交者——见线程池。

常见误区

[!warning] 常见误区:catch (Exception e) {} 静默吞掉会把故障变成数据错误,极难排障。

[!warning] 常见误区:用异常做正常分支导流 成功路径高频场景用返回值/Optional;异常给“失败路径”。

工程实践

  1. 公共模块定义稳定异常类型。
  2. 控制器/入口层统一映射 HTTP/错误码。
  3. 日志含业务关键 id,避免只打堆栈。
  4. 测试既要测成功也要测异常契约。

本节总结

  • 异常是类型化失败控制流
  • 受检/非受检是 API 设计选择
  • 边界处理 + 原因链 + 不吞异常

自测题

  1. Error 为什么通常不在业务里 catch 恢复?
  2. 何时包装异常而不是原样抛出?
参考答案
  1. 多表示环境/JVM 级严重问题,业务恢复策略通常不成立。
  2. 跨越抽象边界时转译为调用方语义,同时保留 cause。

延伸阅读与资料来源

资料类型支撑内容
Exceptions (Oracle Tutorial)教程异常模型
JLS Exceptions规范异常语义
创建于 2026/7/15 更新于 2026/7/15