Java庫包含很多資源厕诡,這些資源必須手動調(diào)用close方法關(guān)閉蝗锥。這樣的例子包括InputStream、OutputStream,和java.sql.Connection此迅。關(guān)閉資源常常被客戶端忽略铁孵,可以預見會有可怕的性能后果锭硼。許多這些資源用finalizer作為安全保障,但是finalizer不會很好的工作(條目8)蜕劝。
歷史上檀头,一個資源應該正確地關(guān)閉,即使面對一個異澄跤睿或者返回鳖擒,try-finally語句是最好的保證方式:
// try-finally - 不再是關(guān)閉資源最好的方式!
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
這可能看上去不太糟,但是當你添加第二個資源時會變得更糟:
// 當用于多于一個資源時烫止,try-finally 是丑陋的!
static void copy(String src, String dst) throws IOException {
try {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
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ù)時間會把這個弄錯。一開始馆蠕,我在Java Puzzlers [Bloch05] 88頁把這個弄錯了期升,而且?guī)啄隂]人注意到惊奇。事實上在2007年,Java庫的close方法的使用播赁,三分之二是錯誤的颂郎。
用try-finally語句關(guān)閉資源的正確代碼,就像前面兩個代碼例子容为,甚至有微妙的缺陷乓序。在兩個try代碼塊和finally代碼塊中的代碼可能會拋出異常。比如坎背,firstLineOfFile方法中替劈,由于底層物體設備的失敗,調(diào)用readLine可能拋出一個異常得滤,而且close方法調(diào)用可能因為同樣的原因失敗陨献。在這些情形下,第二個異常完全掩蓋了第一個懂更。在異常棧信息中沒有第一個異常的記錄眨业,在實際系統(tǒng)中這個可能使得調(diào)試非常復雜,通常為了診斷這個問題沮协,第一個異常是我們想要看見的龄捡。為了第一個異常而抑制第二個,雖然編寫這樣的代碼是可能的皂股,事實上墅茉,沒有人這么干,因為這太啰嗦了呜呐。
當Java 7引進了try-with-resource語句[JLS, 14.20.3],這些問題全解決了悍募。為了用在這個結(jié)構(gòu)蘑辑,一個資源必須實現(xiàn)AutoCloseable接口,這個接口包含一個返回空的close方法坠宴。Java庫和第三方庫中的許多類和接口現(xiàn)在已經(jīng)實現(xiàn)或者擴展了AutoCloseable洋魂。如果你編寫一個必須關(guān)閉資源的類,你的類也應該實現(xiàn)AutoCloseable喜鼓。
下面是我們第一個例子使用try-with-resource的樣子:
// try-with-resources - 關(guān)閉資源的最好方式副砍!
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
下面是我們第二個例子使用try-with-resource的樣子:
// 多個資源的try-with-resources - 簡短而明了
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];
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
不僅try-with-resource版本比原來更加簡短和可讀,而且它們提供了好得多的可診斷性庄岖』眙幔考慮firstLineOfFile方法。如果異常由兩個readLine調(diào)用和(不可見的)close方法拋出隅忿,為了前面的異常心剥,后面的抑制了邦尊。事實上,為了保護你真實想看到的異常优烧,多個異巢踝幔可以被抑制。這些抑制的異常沒有被丟棄畦娄,它們打印在在一個堆棧信息里面又沾,用一個注釋說明它們被抑制了。你也可以用getSuppressed方法以編程方式獲取它們熙卡,在Java 7中這個方法添加到了Throwable杖刷。
你可以把catch子句放在try-with-resource語句,就像你可以放在try-finally語句一樣再膳。這讓你可以處理異常挺勿,而沒有用另外一層嵌套來污染你的代碼。作為有點特別的例子喂柒,下面是firstLineOfFile方法的一個版本不瓶,它不拋出異常,但是如果不能夠打開或者讀取文件灾杰,它返回一個默認值:
// 有catch子句的try-with-resource
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)閉的資源的時候蚊丐,總是優(yōu)先使用try-with-resource,而不是try-finally艳吠。這樣的代碼更加簡短和清晰麦备,而且它產(chǎn)生的異常也更加有用。對于使用必須關(guān)閉的資源的代碼昭娩,try-with-resource語句使得正確編寫這些代碼更容易凛篙,而用try-finally是在實踐中不可能的。