try-with-resources 语句

Version >= Java SE 7

正如 try-catch-final 语句示例所示,使用 finally 子句进行资源清理需要大量的样板代码才能正确实现边缘情况。Java 7 以 try-with-resources 语句的形式提供了一种更简单的方法来处理这个问题。

什么是资源?

Java 7 引入了 java.lang.AutoCloseable 接口,允许使用 try-with-resources 语句管理类。实现 AutoCloseable 的类的实例称为资源。这些通常需要及时处理,而不是依靠垃圾收集器来处理它们。

AutoCloseable 接口定义了一个方法:

public void close() throws Exception

close() 方法应以适当的方式处理资源。规范声明在已经处理的资源上调用该方法应该是安全的。另外,强烈建议实现 Autocloseable 的类声明 close() 方法抛出比 Exception 更具体的异常,或者根本没有例外。

各种标准 Java 类和接口实现了 AutoCloseable。这些包括:

  • InputStreamOutputStream 及其子类
  • ReaderWriter 及其子类
  • SocketServerSocket 及其子类
  • Channel 及其子类,和
  • JDBC 接口 ConnectionStatementResultSet 及其子类。

应用程序和第三方类也可以这样做。

基本的 try-with-resource 语句

try-with-resources 的语法基于经典的 try-catchtry-finallytry-catch-finally 形式。这是一个基本形式的例子; 即没有 catchfinally 的形式。

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

try 子句之后,要管理的资源在 (...) 部分中声明为变量。在上面的例子中,我们声明了一个资源变量 stream 并将其初始化为新创建的 PrintStream

资源变量初始化后,执行 try 块。完成后,将自动调用 stream.close() 以确保资源不会泄漏。请注意,无论块如何完成,都会发生 close() 调用。

增强的 try-with-resource 语句

试穿与资源语句可以用 catchfinally 块增强,与 Java 的前 7 的 try-catch-终于语法。下面的代码片段为我们之前的代码片段添加了一个 catch 块来处理 PrintStream 构造函数可以抛出的 FileNotFoundException

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
} catch (FileNotFoundException ex) {
    System.err.println("Cannot open the file");
} finally {
    System.err.println("All done");
}

如果资源初始化或 try 块抛出异常,则将执行 catch 块。finally 块将始终执行,就像传统的 try-catch-finally 语句一样。

但有几点需要注意:

  • 资源变量超出catchfinally的范围
  • 资源清理将在语句尝试匹配 catch 块之前发生。
  • 如果自动资源清理引发异常,则可以在其中一个 catch 块中捕获。

管理多个资源

上面的代码段显示了一个被管理的资源。事实上, try-with-resources 可以在一个语句中管理多个资源。例如:

try (InputStream is = new FileInputStream(file1);
     OutputStream os = new FileOutputStream(file2)) {
    // Copy 'is' to 'os'
}

这表现得像你期望的那样。isos 都在 try 块结束时自动关闭。有几点需要注意:

  • 初始化发生在代码顺序中,后来的资源变量初始化程序可以使用先前的值。
  • 将清除已成功初始化的所有资源变量。
  • 资源变量按其声明的相反顺序进行清理。

因此,在上面的例子中,isos 之前被初始化并且在它之后被清理,并且如果在初始化 os 时存在异常则将清除 is

try-with-resource 和经典 try-catch-finally 的等价性

Java 语言规范根据经典的 try-catch-finally 语句指定 try-with-resource 表单的行为。 (有关详细信息,请参阅 JLS。) **

例如,这个基本的资源尝试

try (PrintStream stream = new PrintStream("hello.txt")) {
    stream.println("Hello world!");
}

被定义为等同于此 try-catch-finally

// Note that the constructor is not part of the try-catch statement
PrintStream stream = new PrintStream("hello.txt");

// This variable is used to keep track of the primary exception thrown
// in the try statement. If an exception is thrown in the try block,
// any exception thrown by AutoCloseable.close() will be suppressed.
Throwable primaryException = null;

// The actual try block
try {
    stream.println("Hello world!");
} catch (Throwable t) {
    // If an exception is thrown, remember it for the finally block
    primaryException = t;
    throw t;
} finally {
    if (primaryException == null) {
        // If no exception was thrown so far, exceptions thrown in close() will
        // not be caught and therefore be passed on to the enclosing code.
        stream.close();
    } else {
        // If an exception has already been thrown, any exception thrown in
        // close() will be suppressed as it is likely to be related to the
        // previous exception. The suppressed exception can be retrieved
        // using primaryException.getSuppressed().
        try {
            stream.close();
        } catch (Throwable suppressedException) {
            primaryException.addSuppressed(suppressedException);
        }
    }
}

(JLS 指定实际的 tprimaryException 变量对于普通的 Java 代码是不可见的。)

增强形式的 try-with-resources 被指定为与基本形式的等价。例如:

try (PrintStream stream = new PrintStream(fileName)) {
    stream.println("Hello world!");
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}

相当于:

try {
    try (PrintStream stream = new PrintStream(fileName)) {
        stream.println("Hello world!");
    }
} catch (NullPointerException ex) {
    System.err.println("Null filename");
} finally {
    System.err.println("All done");    
}