1. 程式人生 > >Effective Java 第三版讀書筆記——條款9:使用 try-with-resources 語句替代 try-finally 語句

Effective Java 第三版讀書筆記——條款9:使用 try-with-resources 語句替代 try-finally 語句

Java 類庫中包含許多必須手動呼叫 close 方法來關閉的資源, 比如InputStreamOutputStreamjava.sql.Connection

從以往來看,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-finally 語句關閉資源的正確程式碼(如前面兩個程式碼示例所示)也有一個微妙的缺陷。 try 塊和 finally 塊中的程式碼都可以丟擲異常。 例如,在 firstLineOfFile 方法中,由於底層物理裝置發生故障,對 readLine 方法的呼叫可能會引發異常,並且由於相同的原因,呼叫 close 方法可能也會失敗。 在這種情況下,第二個異常完全沖掉了第一個異常。 在異常堆疊跟蹤中沒有第一個異常的記錄,這可能使實際系統中的除錯變得非常複雜——通常你想要看到第一個異常來診斷問題。 雖然可以編寫程式碼來抑制第二個異常,但是實際上沒有人這樣做,因為它太冗長了。

當 Java 7 引入了 try-with-resources 語句時,所有這些問題都得到了解決。要使用這個構造,資源必須實現 AutoCloseable 介面,該介面由一個返回型別為 voidclose 方法組成。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 語句中一樣。這允許你處理異常,而不會用另一層巢狀汙染程式碼。下面是一個稍微有些不自然的例子,它不會丟擲異常,但是如果它不能開啟或讀取檔案,則返回預設值:

// 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 語句。 這會使生成的程式碼更簡潔、更清晰,並且丟擲的異常在除錯時更有用。