Java類庫裏包含了必須通過調用close方法來手動關閉的資源。比如InputStream,OutputStream還有java.sql.Connection。
關閉資源這個動作通常被客戶端忽視了,其性能表現也可想而知。雖然大部分這些資源都使用終結方法作爲最後的安全線,但終結方法的效果並不是很好。
Java SE 7之前
在過去(Java7之前)的實踐當中,try-finally語句是保證一個資源被恰當關閉的最好的方式,即使是在程序拋出異常或者返回的情況下:
// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(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();
}
}複製代碼
try-finall缺點
即使對於正確使用了try-finally語句的代碼,如前面所示,也有個不起眼的缺點。無論是try裏面的代碼還是finally裏面的代碼,都有可能拋出異常。
無論是try裏面的代碼還是finally裏面的代碼,都有可能拋出異常。例如,在firstLineOfFile方法裏,如果底層物理設備出了故障,則在調用readLine方法時會拋出異常,而且由於相同的原因,調用close方法也會失敗。在這種情況下,第二種異常覆蓋了第一種異常。在異常錯誤棧裏將沒有第一種異常的記錄,這會使實際系統的調試變得很複雜,因爲很多時候你是想查看第一種異常來診斷問題。(異常屏蔽的情況)
Java SE 7之後
當Java 7引入try-with-resources
語句時,所有問題突然一下子解決了。
實現
若要使用這個語句,一個資源必須實現AutoCloseable接口,而這個接口只有一個返回類型爲void的close(void-returning)方法。
java.lang.AutoCloseable 接口中 close() 方法的定義意味着可能拋出 java.lang.Exception。
然而,前面的 AutoClose 示例對該方法進行聲明,但並未提及任何檢查到的異常,這是我們有意爲之,部分是爲了說明異常屏蔽。
Java類庫和第三方類庫裏面的許多類和接口現在都實現或繼承了AutoCloseable接口。如果你寫了一個類,這個類代表一個必須被關閉的資源,那麼你的類也應該實現AutoCloseable接口。
建議
可自動關閉類的規範建議避免拋出
java.lang.Exception,優先使用具體的受檢異常,如果預計 close() 方法不會失敗,就不必提及任何受檢異常。此外還建議,不要聲明任何不應被抑制的異常,java.lang.InterruptedException 就是最好的例子。
實際上,抑制該異常並將其附加到另一個異常可能會導致忽略線程中斷事件,使應用程序處於不一致的狀態。
這是我們自主實現AutoCloseable的一個例子:
/**
* 資源類
*/public class Resource implements AutoCloseable {
public void invoke() {
// todo 業務處理
System.out.println("Resource is using");
}
@Override
public void close() throws Exception {
// todo 關閉資源
System.out.println("Resource is closed");
}
}複製代碼
public class CloseResource {
public static void main(String[] args) {
try(Resource resource = new Resource()) {
resource.invoke();
} catch (Exception e) {
e.printStackTrace();
}
}
}複製代碼
下面這個例子展示瞭如何使用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-finally語句那樣,往try-with-resources裏面添加catch子句。
這能讓我們無需在另一層嵌套污染代碼就能處理異常。下面是一個比較刻意的例子,這個版本中的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則幾乎做不到這點。
本文介紹了 Java SE 7 中一種新的用於安全管理資源的語言結構。這種擴展帶來的影響不僅僅是更多的語法糖。事實上,它能位開發人員生成了正確的代碼,消除了編寫容易出錯的樣板代碼的需要。更重要的是,這種變化還伴隨着將一個異常附加到另一個異常的改進,從而爲衆所周知的異常彼此屏蔽問題提供了完善的解決方案。