Java SE 7 新功能與改進:try-with-resources 述句

介紹

try-with-resources 述句可以讓我們在 try 述句中宣告一到數項資源。當我們說某個物件是一項資源時,意味著該物件實作了 java.lang.AutoCloseable 或是它的子介面 java.lang.Closeable,並且當程式不再使用它們時,我們需要將其關閉。try 新增的述句確保每項資源在區段結束時都會被關閉。

早期我們都必須要自行處理資源的關閉,但現在我們可以藉助此一述句來簡化程式。下例會從檔案中讀取第一行,它使用 BufferedReader 物件來讀檔。BufferedReader 是一項資源,並且當程式不再使用它時,我們必須將其關閉:

// Before
static String readFirstLineFromFile(String path) throws IOException {
    BufferedReader in = new BufferedReader(new FileReader(path));
    try {
        return in.readLine();
    } finally {
        if (in != null) {
            try {
                in.close();
           } catch (Exception ex) {
		   }
        }
	}
}

// After
static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader in = new BufferedReader(new FileReader(path))) {
        return in.readLine();
    }
}

本例中我們用新的 try 語法來宣告了一個 BufferedReader 資源,此語句必須接在 try 關鍵字之後,並且用圓括號 () 包起來。BufferedReader 在 Java SE 7 及其後版本裡,已實作了 java.lang.AutoCloseable 介面。因為 BufferedReader 實體是在 try-with-resource 述句中所宣告的,所以它將會被自動關閉;無論是 try 區塊正常結束,或是被突然中斷(像是 BufferedReader.readLine() 丟出 IOException)。

早期 Java 版本中,我們可以用 finally 區塊來確保資源被正確關閉(不管 try 區塊是正常結束或是突然結束)。下例使用 finally 來替代 try-with-resources 述句:

static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
    BufferedReader in = new BufferedReader(new FileReader(path));
    try {
        return in.readLine();
    } finally {
        if (in != null) {
		    in.close();
		}
    }
}

然而在本例中,如果 readLine()close() 方法同時丟出例外,則 readFirstLineFromFileWithFinallyBlock() 方法最終丟出的是 finally 區塊中所產生的例外,而 try 區塊中產生的例外將會被抑制。相反地,在 readFirstLineFromFile() 中,如果 try 區塊及 try-with-resources 述句中同時產生例外,則該方法最終丟出的將會是 try 區塊中所產生的例外,而不是 try-with-resources 述句中產生的例外。

也就是說,如果 readLine()close() 方法同時丟出例外,則 readFirstLineFromFileWithFinallyBlock() 方法會丟出由 close() 方法所丟出的例外,並抑制 readLine() 方法所丟出的例外;相反地 readFirstLineFromFile() 方法會丟出由 readLine() 方法所丟出的例外,並抑制 close()方法所丟出的例外。我們可以取得那些被抑制住的例外,細節請參考最後面的小節。

多個資源管理

我們可以在 try-with-resources 述句中宣告一到多個資源。下例示範了如何從變數名為 zipFileName 的 ZIP 檔案中取得被壓縮的檔案名稱,並且建立一個包含這些檔案名稱的文字檔:

public static void writeToFileZipFileContents(String zipFileName, String outputFileName) throws IOException {
    Charset charset = Charset.forName("US-ASCII");
    Path outputFilePath = Paths.get(outputFileName);

    // Open zip file and create output file with try-with-resources statement
    try (
        ZipFile zf = new ZipFile(zipFileName);
        BufferedWriter writer = Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            // Get the entry name and write it to the output file
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((ZipEntry) entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}

資源關閉順序

本例中 try-with-resources 述句宣告了兩個物件 ZipFileBufferedWriter,並且用分號 ; 分開。當此區塊程式碼運作結束時,無論是正常結束或例外發生,BufferedWriterZipFile 物件的 close() 方法都會自動地依先後順序被呼叫。注意:呼叫所有資源 close() 方法的順序和宣告它們的順序是相反的。

try (Connection conn = getConnection();
     Statement statement = conn.createStatement();
     ResultSet rs = statement.executeQuery(sql)
) {
    // do something
} finally {
    System.out.println("enter");
}

上面的程式片段在 finally 區段中的執行順序如下:

  1. rs.close();
  2. statement.close();
  3. conn.close();
  4. System.out.println("enter");

由此可知:

  1. 呼叫 close() 方法的順序和宣告順序是相反的。
  2. 會先將所有資源都關閉後,才會正式進入 finally 區塊。

注意:try-with-resources 述句可以擁有 catchfinally 區塊,如同原本的 try 述句一樣。在 try-with-resources 述句中,會先將所有被宣告的資源關閉後,才會進入 catchfinally 區塊。

被抑制的例外

在上例 writeToFileZipFileContents() 中,如果 try 區塊中產生了一個例外的話(例如 write() 方法),那麼 try-with-resources 述句中的其他例外全都會被抑制下來(BufferedWriterZipFileclose() 方法)。也就是說最後 writeToFileZipFileContents() 方法丟出的是 try 區塊中的例外(例如 write() 方法)。如果我們想取得那些被抑制的例外的話,可以呼叫 Throwable.getSuppressed() 方法。

try {
    writeToFileZipFileContents("zipFileName.zip", "outputFileName.txt");
} catch (IOException ex) {
    Throwable[] suppressedExceptions = ex.getSuppressed();
    for (Throwable suppressedException : suppressedExceptions) {
		System.out.println(suppressedException.getMessage());
	}
}

實作了 java.lang.AutoCloseablejava.lang.Closeable 介面的類別

請參考 AutoCloseableCloseable 介面的 JavaDoc,上面列出了所有已實作的類別。java.lang.Closeable 介面繼承自 java.lang.AutoCloseable 介面。它們兩個的不同處在於:java.lang.Closeable 介面的 close() 方法會丟出 IOException 類型的例外,而 java.lang.AutoCloseable 介面的 close() 方法丟出的是 Exception 類型的例外。因此,java.lang.AutoCloseable 介面的子類別可以覆寫 close() 方法的行為來丟出特定的例外,像是 IOException,或是根本就不丟出任何例外。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

eleven + six =

返回頂端