try-with-resources按声明逆序关闭资源,即最后声明的先关;资源须实现AutoCloseable接口;close()异常被抑制并附加到主异常;资源变量隐式final且作用域限于try块内。

多个资源必须用分号隔开,且声明顺序决定关闭顺序
try-with-resources 的关闭顺序不是按使用先后,而是严格按括号内声明的**逆序**:最后声明的先关,最先声明的后关。这本质是栈式 LIFO 行为,不是“谁先打开谁先关”。比如包装流依赖底层流时,必须把包装流写在后面,否则底层流提前关闭会导致 IOException(如 Stream closed)。
常见错误是把 BufferedInputStream 写在 FileInputStream 前面:
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("a.txt")); // ❌ 会先关 bis,但 bis.close() 内部还要操作 fis
FileInputStream fis = new FileInputStream("a.txt")) { ... }
正确写法是:
-
FileInputStream先声明,BufferedInputStream后声明 - 这样
BufferedInputStream.close()先执行(刷新缓冲区),再触发FileInputStream.close() - 多资源间无依赖时顺序影响小,但有嵌套/包装关系时必须逆序声明
资源必须实现 AutoCloseable,Scanner 在 Java 19 前不支持
不是所有带 close() 方法的类都能放进 try-with-resources。关键看是否实现 AutoCloseable 接口。可以用 javap -s java.util.Scanner 查签名——Java 19 之前它没实现该接口,所以以下写法在 Java 18 及更早版本会编译失败:
try (Scanner sc = new Scanner(System.in)) { ... } // ❌ Java 18 及以前:Scanner 不是 AutoCloseable
而 FileInputStream、BufferedWriter、Connection、PreparedStatement 都实现了,可直接用。自定义类若要支持,必须显式 implements AutoCloseable 并提供 close() 实现。
- 检查方式:看 Javadoc 或用
javap确认接口继承链 - JDBC 4.0+ 的
ResultSet支持,但旧驱动可能不兼容 - Java 9 起允许复用外部已声明的 effectively final 变量,但变量本身仍需是 AutoCloseable 类型
close() 抛异常会被抑制,主异常仍是 try 块抛出的那个
当 try 块正常结束或抛出异常后,JVM 会依次调用每个资源的 close()。如果某个 close() 也抛异常(比如网络连接已断),它不会覆盖主异常,而是被“抑制”并附加到主异常上。
这意味着你不能只捕获 catch (IOException e) 就认为问题全在业务逻辑里——真正的根因可能藏在被抑制的异常中。
- 通过
e.getSuppressed()获取所有被抑制的异常数组 - 日志里不打印 suppressed 异常,很容易漏掉资源关闭失败的真实原因
- 多个资源关闭都失败时,只有第一个
close()异常成为主异常,其余全被 suppress
资源变量隐式 final,不能在 try 块内重新赋值
在 try 括号里声明的资源变量,编译器自动视为 final。下面代码会编译报错:
try (FileInputStream fis = new FileInputStream("a.txt")) {
fis = new FileInputStream("b.txt"); // ❌ 编译错误:cannot assign a value to final variable 'fis'
}
这不是语法限制,而是设计使然:资源引用必须稳定,才能确保 finally 阶段调用的是最初创建的那个实例的 close()。
- 若需切换资源,应在 try 外声明变量,Java 9+ 支持
try (fis)复用(前提是变量是 effectively final) - 不要试图在 try 块内对资源变量赋新值,那只会让自动关闭失效
- 资源作用域仅限于 try 块内,块外访问会编译报错
IOException,而堆栈里看不到源头。务必按“被包装者先声明、包装者后声明”的原则组织语句。

















