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");    
}