Java類庫裏包含了必須經過調用close方法來手動關閉的資源。好比InputStream,OutputStream還有java.sql.Connection。java
關閉資源這個動做一般被客戶端忽視了,其性能表現也可想而知。雖然大部分這些資源都使用終結方法做爲最後的安全線,但終結方法的效果並非很好。sql
在過去(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();
}
}複製代碼
這麼作看起來可能還沒什麼問題,但當你添加第二個資源時,狀況就開始變得糟糕了:
bash
// 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-finally語句的代碼,如前面所示,也有個不起眼的缺點。不管是try裏面的代碼仍是finally裏面的代碼,都有可能拋出異常。
ide
不管是try裏面的代碼仍是finally裏面的代碼,都有可能拋出異常。例如,在firstLineOfFile方法裏,若是底層物理設備出了故障,則在調用readLine方法時會拋出異常,並且因爲相同的緣由,調用close方法也會失敗。在這種狀況下,第二種異常覆蓋了第一種異常。在異常錯誤棧裏將沒有第一種異常的記錄,這會使實際系統的調試變得很複雜,由於不少時候你是想查看第一種異常來診斷問題。(異常屏蔽的狀況)
性能
當Java 7引入try-with-resources
語句時,全部問題忽然一會兒解決了。ui
若要使用這個語句,一個資源必須實現AutoCloseable接口,而這個接口只有一個返回類型爲void的close(void-returning)方法。spa
意味着可能拋出 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則幾乎作不到這點。