目錄
介紹
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 述句宣告了兩個物件 ZipFile
及 BufferedWriter
,並且用分號 ; 分開。當此區塊程式碼運作結束時,無論是正常結束或例外發生,BufferedWriter
和 ZipFile
物件的 close()
方法都會自動地依先後順序被呼叫。注意:呼叫所有資源 close()
方法的順序和宣告它們的順序是相反的。
try (Connection conn = getConnection();
Statement statement = conn.createStatement();
ResultSet rs = statement.executeQuery(sql)
) {
// do something
} finally {
System.out.println("enter");
}
上面的程式片段在 finally
區段中的執行順序如下:
rs.close();
statement.close();
conn.close();
System.out.println("enter");
由此可知:
- 呼叫
close()
方法的順序和宣告順序是相反的。 - 會先將所有資源都關閉後,才會正式進入
finally
區塊。
注意:try
-with-resources 述句可以擁有 catch
和 finally
區塊,如同原本的 try
述句一樣。在 try
-with-resources 述句中,會先將所有被宣告的資源關閉後,才會進入 catch
和 finally
區塊。
被抑制的例外
在上例 writeToFileZipFileContents()
中,如果 try
區塊中產生了一個例外的話(例如 write()
方法),那麼 try
-with-resources 述句中的其他例外全都會被抑制下來(BufferedWriter
或 ZipFile
的 close()
方法)。也就是說最後 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.AutoCloseable
和 java.lang.Closeable
介面的類別
請參考 AutoCloseable
和 Closeable
介面的 JavaDoc,上面列出了所有已實作的類別。java.lang.Closeable
介面繼承自 java.lang.AutoCloseable
介面。它們兩個的不同處在於:java.lang.Closeable
介面的 close()
方法會丟出 IOException
類型的例外,而 java.lang.AutoCloseable
介面的 close()
方法丟出的是 Exception
類型的例外。因此,java.lang.AutoCloseable
介面的子類別可以覆寫 close()
方法的行為來丟出特定的例外,像是 IOException
,或是根本就不丟出任何例外。