Skip to content

try-with-resources 细节

Java 7 引入的 try-with-resources,平时就是 try (FileInputStream fis = new FileInputStream("a.txt")),很多人也就用到这层。但有些细节值得捋清楚。

一、基本用法

java
// 实现 AutoCloseable 接口就行
try (BufferedReader reader = new BufferedReader(
        new FileReader("a.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} // 自动调用 close()

不用写 finally,也不用担心 close() 漏写。

二、 suppress 异常:close() 抛异常了怎么办

这是很多人没想过的问题。如果 try 块和 close() 都抛异常,谁先谁后?

java
try (MyResource r = new MyResource()) {
    r.doSomething();  // 抛出 IOException
} // close() 也抛出 IOException

实际行为:try 块的异常会被抛出来,close() 的异常会被 suppress。

怎么拿到被 suppress 的异常?

java
} catch (IOException e) {
    // 主异常
    System.out.println("主异常:" + e.getMessage());
    // 被 suppress 的异常
    Throwable[] suppressed = e.getSuppressed();
    for (Throwable t : suppressed) {
        System.out.println("被吞掉的:" + t.getMessage());
    }
}

编译器会自动把 close() 的异常加到 suppress 列表里,不会让主异常丢失。

三、多资源声明顺序

可以同时声明多个资源:

java
try (InputStream is = new FileInputStream("a.txt");
     OutputStream os = new FileOutputStream("b.txt")) {
    is.transferTo(os);
}

关闭顺序:先声明的后关闭,跟 finally 的顺序相反。

java
// 实际执行顺序
// 1. is.transferTo(os)  // 先用
// 2. os.close()          // 后关闭
// 3. is.close()           // 先关闭

为什么这样设计?后声明的可能依赖先声明的,比如 OutputStream 依赖 InputStream 的数据,先关 InputStream 可能导致数据没刷完。

四、变量必须直接声明

java
// 这样不行
MyResource r = new MyResource();
try (r) { }  // 编译错误

// 正确写法
try (MyResource r = new MyResource()) { }

原因:编译器要在字节码层面插入资源初始化逻辑,变量声明必须紧贴着 try。

如果想在 try 外拿到这个资源做其他操作:

java
MyResource r = new MyResource();
try (r) {
    r.use();
}
// r 已经 close 了,不能再用

五、catch 和 finally 还能用

try-with-resources 不影响加 catch 和 finally:

java
try (InputStream is = new FileInputStream("a.txt")) {
    // 正常业务
} catch (IOException e) {
    // 处理异常
} finally {
    // 额外清理逻辑(资源已经在 try 声明里自动关了)
}

六、返回值会吞异常吗

java
static String test() {
    try (MyResource r = new MyResource()) {
        return r.read();  // 这里抛异常
    }  // close() 也抛异常
}

返回的是 try 块的返回值,close() 的异常被 suppress 了。如果想拿到 close() 的异常,照样用 getSuppressed()

七、常见错误

错误1:以为 try 块抛异常就不会 close

java
try (Connection conn = dataSource.getConnection()) {
    throw new RuntimeException("业务异常");
} // 一样会 close()

错误2:把普通对象放进 try

java
// 编译通过,但毫无意义
try (new Object()) { }  // Object 没实现 AutoCloseable

错误3:关了还想用

java
try (BufferedReader br = new BufferedReader(...)) {
    return br.readLine();
}
// 返回时已经 close 了,调用方拿到的 Reader 已经废了

总结

细节说明
close() 异常会被 suppressgetSuppressed()
多资源关闭顺序先声明的后关
变量必须直接声明编译器需要
catch/finally 还能加不冲突
不会漏 close总会执行

用 try-with-resources 能少写很多 finally,也更安全,至少不用担心资源泄漏。