自動嘗試關閉資源


經常地,在使用tryfinally嘗試關閉資源時,會發現程式撰寫的流程是類似的,就如 使用 finallyFileUtil示範的,你會先檢查scanner是否為null,再呼叫close()方法關閉Scanner。在JDK7之後,新增了嘗試關閉資源(try-with-resources)語法,直接來看如何使用:

package cc.openhome;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class FileUtil2 {
    public static String readFile(String name) throws FileNotFoundException {
        StringBuilder builder = new StringBuilder();
        try(Scanner scanner = new Scanner(new FileInputStream(name))) {
            while (scanner.hasNext()) {
                builder.append(scanner.nextLine());
                builder.append('\n');
            }
        } 
        return builder.toString();
    }
}

正如程式示範的,想要嘗試自動關閉資源的物件,是撰寫在try之後的括號中,如果無需catch處理任何例外,可以不用撰寫,也不用撰寫finally自行嘗試關閉資源。

JDK7的嘗試關閉資源(try-with-resources)語法也是個編譯器蜜糖,嘗試反組譯觀察看看,有助於瞭解這個語法是否符合你的需求:

...
public static String readFile(String name) throws FileNotFoundException {
    StringBuilder builder = new StringBuilder();
    Scanner scanner = new Scanner(new FileInputStream(name));
    Throwable localThrowable2 = null;
    try {
        while (scanner.hasNext()) {
            builder.append(scanner.nextLine());
            builder.append('\n');
        }
    } catch (Throwable localThrowable1) {  // 嘗試捕捉所有錯誤
        localThrowable2 = localThrowable1;
        throw localThrowable1;
    }
    finally {
        if (scanner != null) {  // 如果scanner參考至Scanner實例
            if (localThrowable2 != null) { // 若先前有catch到其它例外
                try {
                    scanner.close();          // 嘗試關閉Scanner實例
                } catch (Throwable x2) {    // 萬一關閉時發生錯誤
                    localThrowable2.addSuppressed(x2);   // 在原例外物件中記錄
                }
            } else {
                scanner.close();  // 若先前沒有發生任何例外,就直接關閉Scanner
            }
        }
    }

    return builder.toString();
}
...

重要的部份,直接在程式碼中以註解方式說明了。若一個例外被catch後的處理過程引發另一個例外,通常會拋出第一個例外作為回應,addSuppressed()方法是JDK7在java.lang.Throwable中新增的方法,可將第二個例外記錄在第一個例外之中,JDK7中與之相對應的是getSuppressed()方法,可傳回Throwable[],代表先前被addSuppressed()記錄的各個例外物件。

使用自動嘗試關閉資源語法時,也可以搭配catch。例如也許你想在發生FileNotFoundException時顯示堆疊追蹤訊息:

...
public static String readFile(String name) throws FileNotFoundException {
    StringBuilder builder = new StringBuilder();
    try(Scanner scanner = new Scanner(new FileInputStream(name))) {
        while (scanner.hasNext()) {
            builder.append(scanner.nextLine());
            builder.append('\n');
        }
    } catch(FileNotFoundException ex) {
        ex.printStackTrace();
        throw ex;
    }

    return builder.toString();
}
...

反組譯後可以看到,實際上前一個反組譯程式片段中粗體字部份,是產生在另一個trycatch區塊中:

...
public static String readFile(String name) throws FileNotFoundException {
    StringBuilder builder = new StringBuilder();
    try {
        // 這個區塊中是自動嘗試關閉資源語法展開後的程式碼
        Scanner scanner = new Scanner(new FileInputStream(name));
        Throwable localThrowable2 = null;
        try {
            while (scanner.hasNext()) {
                builder.append(scanner.nextLine());
                builder.append('\n');
            }
        } catch (Throwable localThrowable1) {
            localThrowable2 = localThrowable1;
            throw localThrowable1;
        }
        finally {
            if (scanner != null) {
                if (localThrowable2 != null) {
                    try {
                        scanner.close();
                    } catch (Throwable x2) {
                        localThrowable2.addSuppressed(x2);
                    }
                } else {
                    scanner.close();
                }
            }
        }
    } catch(FileNotFoundException ex) {
        ex.printStackTrace();
        throw ex;
    }

    return builder.toString();
}
...

使用自動嘗試關閉資源語法時,並不影響你對特定例外的處理,實際上,自動嘗試關閉資源語法也僅協助你關閉資源,而非用於處理例外。

從反組譯的程式碼中也可以看到,使用嘗試關閉資源語法時,不要試圖自行撰寫程式碼關閉資源,這樣會造成重複呼叫close()方法,實際上語意也是如此,既然要自動關閉資源了,又何必自行撰寫程式碼來關閉呢?