《Effective Java》-- 使用try-with-resources關閉資源

Java類庫裏包含了必須經過調用close方法來手動關閉的資源。好比InputStream,OutputStream還有java.sql.Connection。java

關閉資源這個動做一般被客戶端忽視了,其性能表現也可想而知。雖然大部分這些資源都使用終結方法做爲最後的安全線,但終結方法的效果並非很好。sql

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(); 
    }
}複製代碼

這麼作看起來可能還沒什麼問題,但當你添加第二個資源時,狀況就開始變得糟糕了:
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-finall缺點

即便對於正確使用了try-finally語句的代碼,如前面所示,也有個不起眼的缺點。不管是try裏面的代碼仍是finally裏面的代碼,都有可能拋出異常。ide

不管是try裏面的代碼仍是finally裏面的代碼,都有可能拋出異常。例如,在firstLineOfFile方法裏,若是底層物理設備出了故障,則在調用readLine方法時會拋出異常,並且因爲相同的緣由,調用close方法也會失敗。在這種狀況下,第二種異常覆蓋了第一種異常。在異常錯誤棧裏將沒有第一種異常的記錄,這會使實際系統的調試變得很複雜,由於不少時候你是想查看第一種異常來診斷問題。(異常屏蔽的狀況)性能

Java SE 7以後

Java 7引入try-with-resources語句時,全部問題忽然一會兒解決了。ui

實現

若要使用這個語句,一個資源必須實現AutoCloseable接口,而這個接口只有一個返回類型爲void的close(void-returning)方法。spa

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 中一種新的用於安全管理資源的語言結構。這種擴展帶來的影響不只僅是更多的語法糖。事實上,它能位開發人員生成了正確的代碼,消除了編寫容易出錯的樣板代碼的須要。更重要的是,這種變化還伴隨着將一個異常附加到另外一個異常的改進,從而爲衆所周知的異常彼此屏蔽問題提供了完善的解決方案。
相關文章
相關標籤/搜索