經常地,在使用try
、finally
嘗試關閉資源時,會發現程式撰寫的流程是類似的,就如 使用 finally
中FileUtil
示範的,你會先檢查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();
}
...
反組譯後可以看到,實際上前一個反組譯程式片段中粗體字部份,是產生在另一個try
、catch
區塊中:
...
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()
方法,實際上語意也是如此,既然要自動關閉資源了,又何必自行撰寫程式碼來關閉呢?