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

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章