try-with-resource及其異常抑制
引用處:
Java進階知識點3:更優雅地關閉資源 - try-with-resource及其異常抑制
try-with-resources語句
一、背景
我們知道,在Java程式設計過程中,如果打開了外部資源(檔案、資料庫連線、網路連線等),我們必須在這些外部資源使用完畢後,手動關閉它們。因為外部資源不由JVM管理,無法享用JVM的垃圾回收機制,如果我們不在程式設計時確保在正確的時機關閉外部資源,就會導致外部資源洩露,緊接著就會出現檔案被異常佔用,資料庫連線過多導致連線池溢位等諸多很嚴重的問題。
二、傳統的資源關閉方式
為了確保外部資源一定要被關閉,通常關閉程式碼被寫入finally程式碼塊中,當然我們還必須注意到關閉資源時可能丟擲的異常,於是變有了下面的經典程式碼:
public static void main(String[] args) { FileInputStream inputStream = null; try { inputStream = new FileInputStream(new File("test")); System.out.println(inputStream.read()); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } } } }
熟悉其他語言的朋友可能會開始吐槽了,在C++中,我們可以把關閉資源的程式碼放在解構函式中,在C#中,我們有using程式碼塊。這些語法都有一個共同的特性,讓外部資源的關閉行為與外部資源的控制代碼物件的生命週期關聯,當外部資源的控制代碼物件生命週期終結時(例如控制代碼物件已出作用域),外部資源的關閉行為將被自動呼叫。這樣不僅更加符合面向物件的程式設計理念(將關閉外部資源的行為內聚在外部資源的控制代碼物件中),也讓程式碼更加簡潔易懂。怎麼到了Java這裡,就找不到自動關閉外部資源的語法特性了呢。
三、JDK7及其之後的資源關閉方式
3.1 try-with-resource語法
確實,在JDK7以前,Java沒有自動關閉外部資源的語法特性,直到JDK7中新增了try-with-resource語法,才實現了這一功能。
那什麼是try-with-resource呢?簡而言之,當一個外部資源的控制代碼物件(比如FileInputStream物件)實現了AutoCloseable介面,那麼就可以將上面的板式程式碼簡化為如下形式:
public static void main(String[] args) {
try (FileInputStream inputStream = new FileInputStream(new File("test"))) {
System.out.println(inputStream.read());
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
將外部資源的控制代碼物件的建立放在try關鍵字後面的括號中,當這個try-catch程式碼塊執行完畢後,Java會確保外部資源的close方法被呼叫。程式碼是不是瞬間簡潔許多!
3.2 實現原理
try-with-resource並不是JVM虛擬機器的新增功能,只是JDK實現了一個語法糖,當你將上面程式碼反編譯後會發現,其實對JVM虛擬機器而言,它看到的依然是之前的寫法:
public static void main(String[] args) {
try {
FileInputStream inputStream = new FileInputStream(new File("test"));
Throwable var2 = null;
try {
System.out.println(inputStream.read());
} catch (Throwable var12) {
var2 = var12;
throw var12;
} finally {
if (inputStream != null) {
if (var2 != null) {
try {
inputStream.close();
} catch (Throwable var11) {
var2.addSuppressed(var11);
}
} else {
inputStream.close();
}
}
}
} catch (IOException var14) {
throw new RuntimeException(var14.getMessage(), var14);
}
}
3.3 異常抑制
通過反編譯的程式碼,大家可能注意到程式碼中有一處對異常的特殊處理:
var2.addSuppressed(var11);
這是try-with-resource語法涉及的另外一個知識點,叫做異常抑制。當對外部資源進行處理(例如讀或寫)時,如果遭遇了異常,且在隨後的關閉外部資源過程中,又遭遇了異常,那麼你catch到的將會是對外部資源進行處理時遭遇的異常,關閉資源時遭遇的異常將被“抑制”但不是丟棄,通過異常的getSuppressed方法,可以提取出被抑制的異常。
四、總結
1、當一個外部資源的控制代碼物件實現了AutoCloseable介面,JDK7中便可以利用try-with-resource語法更優雅的關閉資源,消除板式程式碼。
2、try-with-resource時,如果對外部資源的處理和對外部資源的關閉均遭遇了異常,“關閉異常”將被抑制,“處理異常”將被丟擲,但“關閉異常”並沒有丟失,而是存放在“處理異常”的被抑制的異常列表中,你可以通過被try程式碼塊丟擲的異常的Throwable.getSuppressed方法找回被壓抑的異常,如e.getSuppressed()。