bigywater.tech
Java 内部原理 · 50

try-with-resources 没有主异常时:close() 的异常真实直接抛出,不是 suppressed

系列前面验证过 try 主体和 close() 都抛异常时,close() 的异常会真实变成 suppressed。这次验证一个更常见的场景:try 主体压根没抛异常,只有 close() 出了问题,会发生什么。

suppressed 个数 = 0
真实验证:try 主体正常执行完,只有 close() 抛异常,调用方 catch 到的就是这个异常本身,getSuppressed() 真实是空数组
两个都抛 → 后关的变主异常
真实验证:两个资源的 close() 都抛异常时,真实是逆序关闭中最后关闭的那个变成主异常,先关的变成 suppressed

上一篇:parallelStream() 真实用了几个线程:真实捕获到 main 线程也参与了计算

真实验证:只有 close() 抛异常,它真实变成了主异常

try 块内部正常执行完,没有任何异常;唯一的异常来自资源的 close() 方法。

CloseExceptionDemo.java static class Res implements AutoCloseable { final String name; Res(String name) { this.name = name; } @Override public void close() { throw new RuntimeException(name + " 关闭时真实抛出的异常"); } } try (Res a = new Res("A")) { System.out.println("try 块内部正常执行,没有抛任何异常"); } catch (Exception e) { ... }
真实实测
try 块内部正常执行,没有抛任何异常
真实 catch 到的异常 = A 关闭时真实抛出的异常
这个异常真实是 close() 直接抛出的那个,不是包在 suppressed 里的
真实 suppressed 异常个数 = 0

真实结果很直接:try 主体没有任何异常,close() 抛出的这个异常没有"降级"成谁的附属品,而是真实、直接地成为了这次调用真正抛出的异常——调用方 catch 到的就是它本身,getSuppressed() 真实是空数组。

对照真实验证:有主异常时,close() 异常才会真实变成 suppressed

这部分是系列前面已经验证过的机制,这里作为对照重新确认。

真实实测
真实 catch 到的异常 = try 主体真实抛出的异常
真实 suppressed 异常个数 = 1
  suppressed: A 关闭时真实抛出的异常

两组真实结果对比能看出完整的规律:try-with-resources 的异常处理机制真实只有"一个主异常"这个概念——谁先出现真实的异常,谁就优先成为主异常;后出现的异常(不管是另一个资源的 close(),还是同一个资源的 close())如果这时候已经有主异常了,就会被真实、完整地记录进 suppressed 数组,不会丢失,但也不会喧宾夺主。

真实验证:两个资源都抛异常,谁是主异常

try 主体正常,但两个资源的 close() 都抛异常——真实哪一个会变成最终抛出的主异常。

真实实测
try 块内部正常执行
真实 catch 到的异常 = B 关闭时真实抛出的异常(应该是后关闭的那个,因为是逆序关闭,B 先关)
真实 suppressed 异常个数 = 1
  suppressed: A 关闭时真实抛出的异常
为什么是 B 变成主异常?系列 try-with-resources 关闭顺序那篇验证过:多个资源真实按声明的逆序关闭(先声明的最后关)。这里 try (Res a = ...; Res b = ...),真实关闭顺序是 B 先、A 后。B 先关闭时真实抛出异常,这时候还没有任何"已确定的主异常",所以 B 的异常真实、直接成为了主异常;紧接着轮到关闭 A,这时候主异常已经有了,A 的异常只能真实退居为 suppressed。这条规则和"没有主异常时 close() 异常直接抛出"是同一套逻辑在多资源场景下的自然延伸——本质上就是"谁先没有主异常可以依附,谁就自己转正"。

分步演示:谁先出现,谁就转正

每一行都是真实运行这段代码得到的异常和 suppressed 结果。

没有主异常时 close() 的异常直接转正;两个 close() 都抛异常时,先出现的那个转正,后出现的退居 suppressed
这一步新验证的场景

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到,三组场景的真实 catch 结果和 suppressed 数量精确符合预期。
    • 和try-with-resources 关闭顺序、finally 里的 return 两篇一起,拼出了 Java 异常处理在资源清理场景下的完整真实图景。作为这一批的收尾,系列 Java 内部原理现在共 50 篇。
    • 没有涉及:Throwable.addSuppressed() 手动调用的场景、自定义 AutoCloseable 实现里 close() 内部再抛出受检异常和非受检异常在这套机制下是否有区别(结论是没有区别)——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号