ITEM 9: PREFER TRY-WITH-RESOURCES TO TRY-FINALLY
??Java庫包含許多必須通過調用close方法手動關閉的資源,如 InputStream, OutputStream, java.sql.Connection.。關閉資源常常被用戶忽視,其后果可想而知。雖然這些庫包中有許多使用 finalizer 作為安全網,但是 finalizer 并不能很好地工作(第8項)。
??通常,try-finally 語句是確保資源被正確關閉的最佳方法,即使在出現異常或返回時也是如此:
// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(newFileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
??這看起來不錯,但是當你添加第二個資源時,情況就更糟了:
// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE]; int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
??這可能很難相信,但即使是優秀的程序員在大多數情況下也會犯這樣的錯誤。首先,我在Java Puzzlers [Bloch05] 的88頁上做錯了,多年來沒有人注意到這一點。事實上,2007年Java庫中使用close方法的三分之二是錯誤的。即使這樣使用 try-finally 語句關閉資源的正確代碼(如前兩個代碼示例所示)也有一個細微的缺陷:try 塊和finally 塊中的代碼都能夠拋出異常。例如,在firstLineOfFile 方法中,對 readLine 的調用可能會由于底層物理設備中的故障引發異常,而對 close 的調用也可能因為相同的原因而失敗。在這種情況下,第二個異常完全覆蓋了第一個異常。在異常堆棧跟蹤中沒有關于第一個異常的記錄,這可能會使系統中的調試變得非常復雜 —— 通常,為了診斷問題,您希望看到的是第一個異常。雖然可以編寫代碼來抑制第二個異常同時支持第一個異常,但實際上沒有人這樣做,因為它太冗長了。
??當Java 7 引入 try-with-resources 語句時,所有這些問題都一下子解決了。要使用這個結構,資源必須實現 AutoCloseable 接口,該接口包含一個返回 void 的close方法。Java庫和第三方庫中的許多類和接口現在都實現或擴展了 AutoCloseable。如果您編寫了一個表示必須關閉的資源的類,那么您的類也應該實現AutoCloseable。
??下面是第一個使用try-with-resources的示例:
// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
??下面是第二個使用try-with-resources的示例:
// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
??與原始版本相比,TRY-WITH-RESOURCES 版本不僅更短、更易于閱讀,而且提供了更好的調式信息。考慮 firstLineOfFile 方法。如果 readLine 調用和(不可見的)關閉都拋出異常,則后一個異常將被抑制,以支持前一個異常。實際上,可能會抑制多個異常,以便保留您實際上想要看到的異常。這些被抑制的異常不是簡單地被拋棄,它們還被打印在堆棧跟蹤中,并帶有表示它們被抑制的符號。您還可以使用getsuppress 方法以編程方式訪問它們,該方法是在Java 7中添加到Throwable中的。
??您可以將 catch 子句放在 try-with-resources 語句上,就像您可以放在常規try-finally 語句上一樣。這允許您處理異常,而不需要使用另一層嵌套破壞代碼。作為一個稍微有點做作的例子,下面是我們的 firstLineOfFile 方法的一個版本,它不會拋出異常,但是如果它不能打開文件或從文件中讀取,它會返回一個默認值:
// try-with-resources with a catch clause
static String firstLineOfFile(String path, String defaultVal) {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
} catch (IOException e) {
return defaultVal;
}
}
??在處理必須關閉的資源時,始終使用資源 try-with-resources 而不是 try-finally。生成的代碼更短、更清晰,并且生成的異常更有用。try-with-resources 語句使得使用必須關閉的資源編寫正確的代碼變得很容易,而使用 try-finally 實際上是不可能的。