Java Try-With-Resources(Java 带资源的 try 语句)

更新于 2025-12-26

Jakob Jenkov 2019-08-25

Java 的 try-with-resources(带资源的 try)结构是一种异常处理机制,可以在你使用完诸如 InputStream 或 JDBC Connection 等资源后自动关闭它们。为此,你必须在 try-with-resources 块中打开并使用这些资源。当执行离开该块时,无论是否抛出异常(无论是来自 try 块内部,还是在关闭资源时),所有在 try-with-resources 块中打开的资源都会被自动关闭。

Try-with-resources 基础用法

让我们通过一个例子来了解 try-with-resources 是如何工作的:

private static void printFile() throws IOException {
    try(FileInputStream input = new FileInputStream("file.txt")) {
        int data = input.read();
        while(data != -1){
            System.out.print((char) data);
            data = input.read();
        }
    }
}

这个例子展示了如何在 try-with-resources 块中打开一个 FileInputStream,从中读取数据,并在执行离开 try 块后自动关闭该流(无需显式调用 close())。

注意方法中的第一行:

try(FileInputStream input = new FileInputStream("file.txt")) {

这就是 try-with-resources 结构FileInputStream 变量在 try 关键字后的括号内声明,同时被实例化并赋值。

try 块执行完毕后,FileInputStream 会自动关闭。之所以能实现这一点,是因为 FileInputStream 实现了 Java 接口 java.lang.AutoCloseable所有实现了该接口的类都可以用于 try-with-resources 结构中。


Java 9 对 Try-with-resources 的增强

在 Java 9 之前,要自动关闭的资源必须在 try 语句的括号内创建。但从 Java 9 开始,这一限制被放宽了:只要引用资源的变量是 effectively final(实际上不可变),就可以直接在括号中引用该变量。

示例(Java 9+):

private static void printFile() throws IOException {
    FileInputStream input = new FileInputStream("file.txt");
    try(input) {
        int data = input.read();
        while(data != -1){
            System.out.print((char) data);
            data = input.read();
        }
    }
}

注意:input 变量现在在 try 块外部声明和初始化,但在 try 括号中被引用。Java 仍会在 try 块结束时正确关闭它。


使用多个资源

你可以在一个 try-with-resources 块中使用多个资源,它们都会被自动关闭:

private static void printFile() throws IOException {
    try(
        FileInputStream input = new FileInputStream("file.txt");
        BufferedInputStream bufferedInput = new BufferedInputStream(input)
    ) {
        int data = bufferedInput.read();
        while(data != -1){
            System.out.print((char) data);
            data = bufferedInput.read();
        }
    }
}

此例中创建了两个资源:FileInputStreamBufferedInputStream。两者都会在离开 try 块时自动关闭。


资源的关闭顺序

在 try-with-resources 中声明的资源,会按照 与声明顺序相反的顺序 被关闭。

例如,在上一节的例子中:

  1. 先关闭 BufferedInputStream
  2. 再关闭 FileInputStream

这是为了确保包装流(如 BufferedInputStream)先被关闭,底层流(如 FileInputStream)后被关闭,符合资源依赖逻辑。


自定义 AutoCloseable 实现

try-with-resources 不仅适用于 Java 内置类,你也可以在自己的类中实现 AutoCloseable 接口。

AutoCloseable 接口定义如下:

public interface AutoCloseable {
    public void close() throws Exception;
}

下面是一个简单实现:

public class MyAutoClosable implements AutoCloseable {
    public void doIt() {
        System.out.println("MyAutoClosable doing it!");
    }

    @Override
    public void close() throws Exception {
        System.out.println("MyAutoClosable closed!");
    }
}

使用方式:

private static void myAutoClosable() throws Exception {
    try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
        myAutoClosable.doIt();
    }
}

输出结果:

MyAutoClosable doing it!
MyAutoClosable closed!

这表明 try-with-resources 是一种非常强大的机制,无论资源是你自定义的还是 Java 内置的,都能确保正确关闭。


Try-with-resources 的异常处理机制

try-with-resources 的异常处理语义与传统的 try-catch-finally 有所不同。理解这些差异有助于编写更健壮的代码。

基本规则:

  1. 如果 try 块内抛出异常 → 所有资源仍会被自动关闭,然后该异常被向上抛出。
  2. 如果关闭资源时抛出异常 → 其他资源仍会继续关闭;第一个关闭失败的异常会被抛出,其余异常被“抑制”(suppressed)。
  3. 如果 try 块和关闭过程都抛出异常try 块中的异常会被抛出,关闭时的异常会被抑制(添加到主异常的 getSuppressed() 数组中)。

这与传统 try-catch-finally 不同:在 finally 中抛出的异常会覆盖 try 块中的异常。而 try-with-resources 优先保留业务逻辑中的异常。


示例:自定义可抛异常的资源

public class AutoClosableResource implements AutoCloseable {
    private String name = null;
    private boolean throwExceptionOnClose = false;

    public AutoClosableResource(String name, boolean throwExceptionOnClose) {
        this.name = name;
        this.throwExceptionOnClose = throwExceptionOnClose;
    }

    public void doOp(boolean throwException) throws Exception {
        System.out.println("Resource " + this.name + " doing operation");
        if(throwException) {
            throw new Exception("Error when calling doOp() on resource " + this.name);
        }
    }

    @Override
    public void close() throws Exception {
        System.out.println("Resource " + this.name + " close() called");
        if(this.throwExceptionOnClose){
            throw new Exception("Error when trying to close resource " + this.name);
        }
    }
}

单资源测试

public static void tryWithResourcesSingleResource() throws Exception {
    try(AutoClosableResource resourceOne = new AutoClosableResource("One", false)) {
        resourceOne.doOp(false);
    }
}
  • doOp(true) → 抛出异常,close() 异常(如有)被抑制。
  • doOp(false)throwExceptionOnClose=trueclose() 异常被抛出,getSuppressed() 为空。

双资源测试

public static void tryWithResourcesTwoResources() throws Exception {
    try(
        AutoClosableResource resourceOne = new AutoClosableResource("One", true);
        AutoClosableResource resourceTwo = new AutoClosableResource("Two", true)
    ){
        resourceOne.doOp(true); // 抛出异常
        resourceTwo.doOp(false);
    }
}

结果:

  • 主异常:来自 resourceOne.doOp(true)
  • 抑制异常(suppressed):两个 close() 抛出的异常(按关闭顺序:先 Two,后 One)

注意:一旦 try 块抛出异常,后续代码不再执行,因此 resourceTwo.doOp(false) 实际不会运行。


Catch 块的使用

你可以在 try-with-resources 后添加 catch 块,就像普通 try 一样:

try(AutoClosableResource resourceOne = new AutoClosableResource("One", true)) {
    resourceOne.doOp(true);
} catch(Exception e) {
    Throwable[] suppressed = e.getSuppressed();
    throw e;
}
  • 如果 doOp() 抛异常 → 被 catch 捕获,getSuppressed() 包含关闭时的异常。
  • 如果仅 close() 抛异常 → 也被 catch 捕获,但 getSuppressed() 为空。

Finally 块的使用

你也可以添加 finally 块,它会在 catch 之后执行(如果存在):

try(AutoClosableResource resourceOne = new AutoClosableResource("One", true)) {
    resourceOne.doOp(false);
} catch(Exception e) {
    throw e;
} finally {
    throw new Exception("Hey, an exception from the finally block");
}

⚠️ 警告:如果在 finally 块中抛出异常,之前所有的异常(包括 try 和 catch 中的)都会丢失! 它们不会被抑制,也无法通过 getSuppressed() 获取。

因此,应避免在 finally 中抛出异常,或谨慎处理。


手动添加被抑制的异常

你可以使用 Throwable.addSuppressed() 方法手动将异常标记为“被抑制”:

Exception finalException = null;
try(AutoClosableResource resourceOne = new AutoClosableResource("One", true)) {
    resourceOne.doOp(false);
} catch(Exception e) {
    finalException = new Exception("Error...");
    finalException.addSuppressed(e);
    for(Throwable suppressed : e.getSuppressed()){
        finalException.addSuppressed(suppressed);
    }
} finally {
    if(finalException != null){
        throw finalException;
    }
}

通常不需要手动操作,但在某些高级场景下可能有用。


旧式资源管理(Java 7 之前)

在 Java 7 引入 try-with-resources 之前,资源管理非常繁琐且容易出错:

private static void printFile() throws IOException {
    InputStream input = null;
    try {
        input = new FileInputStream("file.txt");
        int data = input.read();
        while(data != -1){
            System.out.print((char) data);
            data = input.read();
        }
    } finally {
        if(input != null){
            input.close(); // close() 本身也可能抛异常!
        }
    }
}

问题:

  • 如果 try 块抛异常,finallyclose() 也抛异常 → 只有 close() 的异常被传播,原始异常丢失。
  • 需要手动判空、嵌套 try-catch,代码冗长。

try-with-resources 正是为了解决这些问题而设计的。


总结

  • try-with-resources 自动管理实现了 AutoCloseable 的资源。
  • Java 9+ 支持引用 effectively final 的外部变量。
  • 异常优先级:try 块异常 > close() 异常(后者被抑制)。
  • 关闭顺序:后声明的资源先关闭。
  • 避免在 finally 中抛异常,否则会掩盖重要异常。