⭐ 推荐:社区规则条款 V1.0

Java 中 try-with-resources 如何正确处理多个资源的关闭顺序?

GJ论坛站长
GJ论坛站长 管理员资深会员 👑黑卡会员
发布于 2026-10-22 22:04 ·7 浏览 ·0 回复
内容摘要

try-with-resources 中多个资源按声明逆序关闭,有依赖时应先声明被依赖资源,使包装类先关闭、底层资源后关闭;close() 抛出的异常会作为 suppressed 挂到主异常上。

学完这篇,你能准确说出 try-with-resources 中多个资源的关闭顺序,并写出资源有依赖、close() 又抛异常时不会踩坑的代码。

第一步:把资源写进 try 的括号里,确认类型实现了 AutoCloseable

这一步的目标是让编译器接管关闭动作,你不需要写 finally。资源类必须实现 java.lang.AutoCloseable(Closeable 是它的子接口),否则括号里写不进去,编译直接报错。

先写一个能观察顺序的测试类,存成 CloseOrderDemo.java:

public class CloseOrderDemo {
    static class Res implements AutoCloseable {
        private final String name;
        Res(String name) { this.name = name; System.out.println("open  " + name); }
        @Override public void close() { System.out.println("close " + name); }
    }

    public static void main(String[] args) {
        try (Res a = new Res("A");
             Res b = new Res("B");
             Res c = new Res("C")) {
            System.out.println("body");
        }
    }
}

多个资源之间用分号分隔,最后一个可以不加分号。

注意:javac 默认编码在 Windows 中文环境下可能不是 UTF-8,含中文注释时加 -encoding UTF-8,否则可能报「编码 GBK 的不可映射字符」。

第二步:记住「后声明先关闭」的规则并编译验证

这一步要拿到实际输出,确认关闭顺序与声明顺序相反。执行:

javac -encoding UTF-8 CloseOrderDemo.java
java CloseOrderDemo

输出为:

open  A
open  B
open  C
body
close C
close B
close A

结论:打开顺序是声明顺序,关闭顺序严格反过来,也就是最后声明的 c 最先关闭,最先声明的 a 最后关闭。

第三步:按依赖关系排列声明顺序

这一步要解决实际问题:当一个资源依赖另一个资源时,声明顺序决定谁先关。规则是「被依赖的写在前面,依赖别人的写在后面」,这样关闭时包装类先关、底层流后关。

try (FileReader fr = new FileReader("data.txt");
     BufferedReader br = new BufferedReader(fr)) {
    System.out.println(br.readLine());
}

这里 br 先关闭(会把缓冲刷出去),fr 后关闭。如果反过来写,语法上就通不过,因为构造 br 时 fr 还不存在。

注意:涉及锁、数据库连接池、事务这类资源时,同样遵循这个顺序——先拿到的锁最后释放。顺序写反不会编译报错,但运行时可能出现「在已关闭的连接上操作」这类异常。

第四步:处理 close() 自己抛异常的情况

这一步要搞清楚异常优先级。close() 也可能失败,Java 的处理方式是:**try 块里的异常作为主异常抛出,关闭时产生的异常被挂到主异常的 suppressed 列表里**,用 Throwable.getSuppressed() 取。

try (Res a = new Res("A")) {
    throw new RuntimeException("body 出错");
} catch (Exception e) {
    System.out.println("主异常: " + e.getMessage());
    for (Throwable s : e.getSuppressed()) {
        System.out.println("被抑制: " + s.getMessage());
    }
}

如果 try 块本身没抛异常,那么最先声明的资源(最后关闭的那个)抛出的异常会成为主异常,其余关闭异常被抑制。所以排查时不要只看第一条堆栈,printStackTrace() 会自动打印 Suppressed: 段落,务必往下翻。

注意:不要用 catch (Exception e) { } 把异常吞掉,那样连 suppressed 信息一起消失,线上排查会变得非常困难。

第五步:Java 9 及以上改用 effectively final 变量

这一步是语法简化。如果资源在 try 之前就要创建(比如需要参与其他逻辑),JDK 9 起可以直接在括号里写变量名:

FileReader fr = new FileReader("data.txt");
BufferedReader br = new BufferedReader(fr);
try (fr; br) {
    System.out.println(br.readLine());
}

要求变量是 final 或「effectively final」(声明后不再被重新赋值)。顺序规则完全不变,仍然是 br 先关、fr 后关。

注意:这个写法在 JDK 8 上会编译失败(unexpected token)。团队项目里先跑 java -version 确认版本,别在 8 的环境里照抄。另外,如果变量值为 null,try-with-resources 会跳过关闭、不抛 NPE,但别依赖这个行为掩盖初始化失败。

小结

  • 多资源用分号分隔写在同一对括号里,打开按声明顺序,关闭严格逆序。
  • 有依赖关系时,被依赖的资源写在前面,包装类写在后面。
  • close() 抛出的异常不会中断其余资源的关闭,它们被压进 suppressed 列表,用 getSuppressed() 查看。
  • try 块无异常时,主异常来自最先声明的资源(最后关闭的那个)。
  • JDK 9+ 可用 try (fr; br) 引用 effectively final 变量,JDK 8 不支持。
  • 别用空 catch 吞异常,否则会丢失 suppressed 信息和真实关闭原因。
本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2397.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~