ITEM 9: PREFER TRY-WITH-RESOURCES TO TRY-FINALLY
??Java庫(kù)包含許多必須通過調(diào)用close方法手動(dòng)關(guān)閉的資源瘾蛋,如 InputStream, OutputStream, java.sql.Connection.俐镐。關(guān)閉資源常常被用戶忽視,其后果可想而知哺哼。雖然這些庫(kù)包中有許多使用 finalizer 作為安全網(wǎng)佩抹,但是 finalizer 并不能很好地工作(第8項(xiàng))。
??通常幸斥,try-finally 語(yǔ)句是確保資源被正確關(guān)閉的最佳方法匹摇,即使在出現(xiàn)異骋龋或返回時(shí)也是如此:
// 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();
}
}
??這看起來不錯(cuò)甲葬,但是當(dāng)你添加第二個(gè)資源時(shí),情況就更糟了:
// 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();
}
}
??這可能很難相信懈贺,但即使是優(yōu)秀的程序員在大多數(shù)情況下也會(huì)犯這樣的錯(cuò)誤经窖。首先,我在Java Puzzlers [Bloch05] 的88頁(yè)上做錯(cuò)了梭灿,多年來沒有人注意到這一點(diǎn)画侣。事實(shí)上,2007年Java庫(kù)中使用close方法的三分之二是錯(cuò)誤的堡妒。即使這樣使用 try-finally 語(yǔ)句關(guān)閉資源的正確代碼(如前兩個(gè)代碼示例所示)也有一個(gè)細(xì)微的缺陷:try 塊和finally 塊中的代碼都能夠拋出異常配乱。例如,在firstLineOfFile 方法中皮迟,對(duì) readLine 的調(diào)用可能會(huì)由于底層物理設(shè)備中的故障引發(fā)異常搬泥,而對(duì) close 的調(diào)用也可能因?yàn)橄嗤脑蚨 T谶@種情況下伏尼,第二個(gè)異常完全覆蓋了第一個(gè)異常忿檩。在異常堆棧跟蹤中沒有關(guān)于第一個(gè)異常的記錄,這可能會(huì)使系統(tǒng)中的調(diào)試變得非常復(fù)雜 —— 通常爆阶,為了診斷問題燥透,您希望看到的是第一個(gè)異常沙咏。雖然可以編寫代碼來抑制第二個(gè)異常同時(shí)支持第一個(gè)異常,但實(shí)際上沒有人這樣做班套,因?yàn)樗唛L(zhǎng)了肢藐。
??當(dāng)Java 7 引入 try-with-resources 語(yǔ)句時(shí),所有這些問題都一下子解決了吱韭。要使用這個(gè)結(jié)構(gòu)窖壕,資源必須實(shí)現(xiàn) AutoCloseable 接口,該接口包含一個(gè)返回 void 的close方法杉女。Java庫(kù)和第三方庫(kù)中的許多類和接口現(xiàn)在都實(shí)現(xiàn)或擴(kuò)展了 AutoCloseable瞻讽。如果您編寫了一個(gè)表示必須關(guān)閉的資源的類,那么您的類也應(yīng)該實(shí)現(xiàn)AutoCloseable熏挎。
??下面是第一個(gè)使用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();
}
}
??下面是第二個(gè)使用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 版本不僅更短、更易于閱讀坎拐,而且提供了更好的調(diào)式信息烦磁。考慮 firstLineOfFile 方法哼勇。如果 readLine 調(diào)用和(不可見的)關(guān)閉都拋出異常都伪,則后一個(gè)異常將被抑制,以支持前一個(gè)異常积担。實(shí)際上陨晶,可能會(huì)抑制多個(gè)異常,以便保留您實(shí)際上想要看到的異常帝璧。這些被抑制的異常不是簡(jiǎn)單地被拋棄先誉,它們還被打印在堆棧跟蹤中,并帶有表示它們被抑制的符號(hào)的烁。您還可以使用getsuppress 方法以編程方式訪問它們褐耳,該方法是在Java 7中添加到Throwable中的。
??您可以將 catch 子句放在 try-with-resources 語(yǔ)句上渴庆,就像您可以放在常規(guī)try-finally 語(yǔ)句上一樣铃芦。這允許您處理異常,而不需要使用另一層嵌套破壞代碼襟雷。作為一個(gè)稍微有點(diǎn)做作的例子刃滓,下面是我們的 firstLineOfFile 方法的一個(gè)版本,它不會(huì)拋出異常嗤军,但是如果它不能打開文件或從文件中讀取谷异,它會(huì)返回一個(gè)默認(rèn)值:
// 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;
}
}
??在處理必須關(guān)閉的資源時(shí)匙奴,始終使用資源 try-with-resources 而不是 try-finally枫耳。生成的代碼更短、更清晰僚饭,并且生成的異常更有用。try-with-resources 語(yǔ)句使得使用必須關(guān)閉的資源編寫正確的代碼變得很容易胧砰,而使用 try-finally 實(shí)際上是不可能的鳍鸵。