finally 資源關閉
June 19, 2022〈要抓還是要拋?〉撰寫的 FileUtil
範例並不是很正確,如果建構 FileInputStream
實例就會開啟檔案,不使用時,應該呼叫 close
關閉檔案。FileUtil
是透過 Scanner
搭配 FileInputStream
來讀取檔案,實際上 Scanner
物件有個 close
方法,可以關閉 Scanner
相關資源與搭配的 FileInputStream`。
使用 finally
那麼要何時關閉資源呢?如下撰寫並不是很正確:
...
public static String readFile(String name) throws FileNotFoundException {
var builder = new StringBuilder();
var scanner = new Scanner(new FileInputStream(name));
while (scanner.hasNext()) {
builder.append(scanner.nextLine());
builder.append('\n');
}
scanner.close();
return builder.toString();
}
...
如果 scanner.close
前發生了任何例外,執行流程就會中斷,因此 scanner.close
就可能不會執行,因此 Scanner
及搭配的 FileInputStream
就不會被關閉。
你想要的是無論如何,最後一定要執行關閉資源的動作,try
、catch
語法還可以搭配 finally
,無論 try
區塊有無發生例外,若撰寫有 finally
區塊,finally
區塊一定會被執行。例如:
package cc.openhome;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FileUtil {
public static String readFile(String name) throws FileNotFoundException {
var builder = new StringBuilder();
Scanner scanner = null;
try {
scanner = new Scanner(new FileInputStream(name));
while (scanner.hasNext()) {
builder.append(scanner.nextLine());
builder.append('\n');
}
} finally {
if(scanner != null) {
scanner.close();
}
}
return builder.toString();
}
}
由於 finally
區塊一定會被執行,這個範例中 scanner
原先是 null
,若 FileInputStream
建構失敗,scanner
就有可能還是 null
,因此在 finally
區塊必須先檢查 scanner
是否有參考物件,有的話才進一步呼叫 close
方法,否則 scanner
參考至 null
又打算呼叫 close
方法,會拋出 NullPointerException
。
如果程式撰寫的流程中先 return
了,而且也有寫 finally
區塊,那 finally
區塊會先執行完後,再將值傳回。例如,下面這個範例會先顯示「finally…」再顯示「1」:
package cc.openhome;
public class Main {
public static void main(String[] args) {
System.out.println(test(true));
}
static int test(boolean flag) {
try {
if(flag) {
return 1;
}
} finally {
System.out.println("finally...");
}
return 0;
}
}
自動嘗試關閉資源
經常地,在使用 try
、finally
嘗試關閉資源時,會發現程式撰寫的流程是類似的,就如 FileUtil
示範的,你會先檢查 scanner
是否為 null
,再呼叫 close
方法關閉 Scanner
。
如果你是這類流程,可以使用嘗試關閉資源(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 {
var builder = new StringBuilder();
try(var scanner = new Scanner(new FileInputStream(name))) {
while (scanner.hasNext()) {
builder.append(scanner.nextLine());
builder.append('\n');
}
}
return builder.toString();
}
}
正如程式示範的,想要嘗試自動關閉資源的物件,是撰寫在 try
之後的括號中,如果無需 catch
處理任何例外,可以不用撰寫,也不用撰寫 finally
自行嘗試關閉資源。
使用自動嘗試關閉資源語法時,也可以搭配 catch
。例如也許你想在發生 ``FileNotFoundException` 時顯示堆疊追蹤訊息:
...
public static String readFile(String name) throws FileNotFoundException {
var builder = new StringBuilder();
try(var 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();
}
...
使用自動嘗試關閉資源語法時,並不影響你對特定例外的處理,實際上,自動嘗試關閉資源語法也僅協助你關閉資源,而非用於處理例外。
AutoClosable
嘗試關閉資源語法可套用的物件,必須實作 java.lang.AutoCloseable
介面,以下是個簡單示範:
package cc.openhome;
public class Main {
public static void main(String[] args) {
try(var res = new Resource()) {
res.doSome();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
class Resource implements AutoCloseable {
void doSome() {
System.out.println("做一些事");
}
@Override
public void close() throws Exception {
System.out.println("資源被關閉");
}
}
執行結果如下:
做一些事
資源被關閉
嘗試關閉資源語法也可以同時關閉兩個以上的物件資源,只要中間以分號區隔。來看看以下的範例,哪個物件資源會先被關閉呢?
package cc.openhome;
public class Main2 {
public static void main(String[] args) {
try(ResourceSome some = new ResourceSome();
ResourceOther other = new ResourceOther()) {
some.doSome();
other.doOther();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
class ResourceSome implements AutoCloseable {
void doSome() {
System.out.println("做一些事");
}
@Override
public void close() throws Exception {
System.out.println("資源Some被關閉");
}
}
class ResourceOther implements AutoCloseable {
void doOther() {
System.out.println("做其它事");
}
@Override
public void close() throws Exception {
System.out.println("資源Other被關閉");
}
}
在 try
的括號中,越後面撰寫的物件資源會越早被關閉。執行結果如下,ResourceOther
實例會先被關閉,然後再關閉 ResourceSome
實例:
做一些事
做其它事
資源Other被關閉
資源Some被關閉