跳到正文
Elaine Blog
返回

异常设计与资源关闭

Java 基础

异常设计回答三个问题:谁能处理、需要保留哪些上下文、资源如何收尾。把所有异常捕获后返回 null,只是把一个明确 失败变成更难定位的后续错误。

Table of contents

Open Table of contents

建立失败分类

**受检异常(Checked Exception)**必须在方法签名声明或被捕获,例如 IOException。**非受检异常(Unchecked Exception)**继承 RuntimeException,编译器不强制处理。

分类不要只看继承关系,还要看恢复责任:

失败示例合适的处理者
业务拒绝余额不足、库存不足接口层转换成明确结果
输入或编程错误参数为 null、状态不可能尽早失败并修复代码
短暂基础设施故障超时、连接重置有幂等保障的调用层重试
永久基础设施故障文件不存在、权限拒绝终止操作并保留原因

不要用异常表示正常分支,例如“列表没有元素”。也不要仅凭“受检还是非受检”决定是否重试。

运行资源关闭示例

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

public class 文件首行读取器 {
    static String 读取(Path path) {
        try (var reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
            String line = reader.readLine();
            if (line == null) throw new IllegalArgumentException("文件为空: " + path);
            return line;
        } catch (IOException e) {
            throw new 读取失败异常("读取文件失败: " + path.toAbsolutePath(), e);
        }
    }

    static final class 读取失败异常 extends RuntimeException {
        读取失败异常(String message, Throwable cause) { super(message, cause); }
    }
}

try-with-resources 会在代码块结束时调用 AutoCloseable.close(),即使读取中途抛出异常也会关闭资源。若业务操作和 关闭动作都失败,前者是主异常,关闭异常会出现在 getSuppressed() 中,不会悄悄丢失。

保留异常链

转换异常时必须把原异常作为 cause 传入。错误消息加入当前层掌握的上下文,例如文件绝对路径或订单号,但不要加入 密码、Token、完整身份证号等敏感数据。

throw new 读取失败异常("读取文件失败: " + path.toAbsolutePath(), e);

以下写法会破坏根因:

catch (IOException e) {
    throw new RuntimeException("失败"); // 丢失原异常和操作对象
}

重试前先证明幂等

**幂等(Idempotency)**是同一操作执行一次和多次产生相同业务结果。读取通常可重试;“扣款后连接断开”不能直接重试, 因为第一次可能已经成功。重试策略至少要限制次数、设置退避,并只覆盖明确的短暂错误。

下一步

阅读集合框架的选择与复杂度,学习用数据结构表达语义。


分享这篇文章:

上一篇
面向对象不是堆类:封装、组合与多态
下一篇
集合框架的选择与复杂度