天天看點

Java 7 try-with-resources 代替 try-finally

轉載自:

http://www.cnblogs.com/IcanFixIt/p/8142615.html

Java類庫中包含許多必須通過調用

close

方法手動關閉的資源。 比如

InputStream

OutputStream

java.sql.Connection

。 客戶經常忽視關閉資源,其性能結果可想而知。 盡管這些資源中有很多使用finalizer機制作為安全網,但finalizer機制卻不能很好地工作(條目 8)。

從以往來看,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();
    }
}           

這可能很難相信,但即使是優秀的程式員,大多數時候也會犯錯誤。首先,我在Java Puzzlers[Bloch05]的第88頁上弄錯了,多年來沒有人注意到。事實上,2007年Java類庫中使用

close

方法的三分之二都是錯誤的。

即使是用try-finally語句關閉資源的正确代碼,如前面兩個代碼示例所示,也有一個微妙的缺陷。 try-with-resources塊和finally塊中的代碼都可以抛出異常。 例如,在

firstLineOfFile

方法中,由于底層實體裝置發生故障,對

readLine

方法的調用可能會引發異常,并且由于相同的原因,調用

close

方法可能會失敗。 在這種情況下,第二個異常完全沖掉了第一個異常。 在異常堆棧跟蹤中沒有第一個異常的記錄,這可能使實際系統中的調試非常複雜——通常這是你想要診斷問題的第一個異常。 雖然可以編寫代碼來抑制第二個異常,但是實際上沒有人這樣做,因為它太冗長了。

當Java 7引入了try-with-resources語句時,所有這些問題一下子都得到了解決[JLS,14.20.3]。要使用這個構造,資源必須實作

AutoCloseable

接口,該接口由一個傳回為

void

close

組成。Java類庫和第三方類庫中的許多類和接口現在都實作或繼承了

AutoCloseable

接口。如果你編寫的類表示必須關閉的資源,那麼這個類也應該實作

AutoCloseable

接口。

以下是我們的第一個使用try-with-resources的示例:

// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(
           new FileReader(path))) {
       return br.readLine();
    }
}           

以下是我們的第二個使用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-with-resources版本比原始版本更精簡,更好的可讀性,而且它們提供了更好的診斷。 考慮

firstLineOfFile

方法。 如果調用

readLine

和(不可見)

close

方法都抛出異常,則後一個異常将被抑制(suppressed),而不是前者。 事實上,為了保留你真正想看到的異常,可能會抑制多個異常。 這些抑制的異常沒有呗被抛棄, 而是列印在堆棧跟蹤中,并标注為被抑制了。 你也可以使用

getSuppressed

方法以程式設計方式通路它們,該方法在Java 7中已添加到的

Throwable

中。

可以在 try-with-resources語句中添加catch子句,就像在正常的try-finally語句中一樣。這允許你處理異常,而不會在另一層嵌套中污染代碼。作為一個稍微有些做作的例子,這裡有一個版本的

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語句實際上是不可能的。