Java SE 7 版引進三種例外處理新機制
其中之一為 Try-with-resource 語句
Try-with-resource
Try-with-resource 語法其實非常簡單
語法為
try ( resource assignment 1; resource assignment 2; ...... ) { // code of process }
resource 必須是實作 java.io.Closeable 的 class
敘述中可以指派多個 resource,以分號分隔
最後面是否有分號無所謂,也就是說可以將其視為正常的敘述
也就是說只是在正常的 try 後面加上括弧列表而已
可是注意,寫空括弧是錯的呀,沒有要用這個機制乾脆就不要寫
Try-with-resource 的一項特點
就是指定在 resource 區的物件,在此敘述執行結束後會自動執行關閉動作
關閉順序與 resource 指派順序相反
看到這句話就該注意,會自動執行動作
就代表一定要有個固定名稱的 method 存在
如何確保這個 method 一定存在呢?沒錯,就是實作介面
Java 最低要求 resource 必須要實作 java.io.Closeable
啊?在 java.io 裡呀?
這個介面至少在 SE 6 版就已經存在了
原因筆者也不是很瞭解,可能就電腦系統本身
能關閉的也只有電腦裝置的存取,也就是對 CPU 而言屬於 I/O 的部分
但是現在因為有了這樣的機制,而且沒有人規定能關閉的只有 I/O 設備
並且 java.io.Closeable 的關閉介面定義
public void close() throws java.io.IOException
這樣又侷限能夠拋出的例外,就一般應用而言是不合邏輯
因此必須將其一般化,因此 SE 7 版建立了 java.lang.AutoCloseable 介面
java.lang.AutoCloseable 的關閉介面定義為
public void close() throws java.lang.Exception
這樣就非常適合一般應用了
而且仔細看喔,使用 try-with-resource
除了 try 區塊,catch 或 finally 區塊可以連寫都不需要
為什麼要有這樣的機制?
至於為什麼要設計這樣的機制
因為很多寫程式的人很不小心,常常開了忘記關
這當然也是一個因素,但是並不全然只有這個問題
因為有大部分的情況,忘記關是也不會發生什麼事
要注意,這個語法設計在 try 上
也就是說它和例外有很大的關係
按照 Oracle 官方文件所描述的
不用 try-with-resource,也可以用 finally 區塊達成目的
finally區是無論 try 或 catch 區有沒有被完整執行
都一定會被執行到的區塊
可是 I/O 類型的 class,物件關閉是有可能會拋出例外的
假設 try 區塊拋出了例外,finally 的關閉也拋出例外會如何?
答案是,這個執行區最後會拋出 finally 所送出的例外
try 區塊的例外會因此被抑制
這通常不會是程式開發人員希望看到的
因為 try 區塊,也就是主流程發生錯誤是比較優先重要的
範例
// the output will be "Throw When Closing" public class Test { public static void main(String[] args){ try { test(); } catch (Exception e){ System.out.println(e.getMessage()); } } private static void test() throws Exception { TestClass c = new TestClass(); try { c.example(); } finally { c.close(); } } private static class TestClass { public void example() throws Exception {
throw new Exception("Throw Example"); } public void close() throws Exception { throw new Exception("Throw When Closing"); } } }
當使用 try-with-resource 敘述時
如果上述的情形發生時,close 所拋出的例外就會被抑制
也就是說,最後拋出的例外是源自 try 區塊
// the output will be "Throw From Example" public class Test { public static void main(String[] args){ try { test(); } catch (Exception e){ System.out.println(e.getMessage()); } } private static void test() throws Exception { try (TestClass c = new TestClass()) { c.example(); } } private static class TestClass implements AutoCloseable { public void example() throws Exception { throw new Exception("Throw Example"); } public void close() throws Exception { throw new Exception("Throw When Closing"); } } }
最後補充些自己實驗展現的特性
一句話處處是重點
前面提到指定在 resource 區的物件,在此敘述執行結束後會自動執行關閉動作
就是真的只有 resource 區物件而已
我們可以試著在物件建立時,賦予物件 ID
並且在關閉的時候打印訊息「"Close " + ID」
// The only output is "Close 2" private static void test() throws Exception { TestClass co = new TestClass(1); try (TestClass c = new TestClass(2);) { TestClass ci = new TestClass(3); } }
最後呈現的打印結果是 "Close 2"
就算 try 區塊有使用到 co 也是一樣的
指派並不一定要以 new
也可以指派已經建立好的物件在 resource 區塊內
所以以下的寫法,是完全成立的
private static void test(TestClass input) throws Exception { try (TestClass c = input) { } // Object is closed here }
但是要注意因為是在 try-with-resource 敘述執行一結束
resource 物件就會執行關閉動作
也就是說這個 method 執行結束
傳進去的 resource 也被關了
啊~~,怎麼那麼不方便啊?!
嗯...如果認為不方便,這時候應該思考的是
為什麼不把 try-with-resource 寫在這個 method 外面
一般除了特殊目的,不會有人刻意要這樣寫的
不是所有例外都會被抑制
前面也提到 try 區塊拋出的例外
會抑制 resource 關閉所拋出的例外
可是這不代表使用之後連 finally 拋出的例外也會被抑制住
try-catch-finally 機制除了多了 resource 指派和自動關閉外
其他一切還是照著原本的程序走的,沒有被修改
所以以下的例子,最後拋出的例外仍舊是由 finally 產生
private static void test() throws Exception { try (TestClass c = new TestClass(1)) { c.example(); } finally { throw new Exception("Throw in finally"); } }
另外當 resource 指派的時候發生例外
try-with-resource也不會讓這個 resource 被抑制住
這個是很合理的,因為連指派都失敗了
更別提後續的命令這個 resource 執行的任何步驟
private static void test() throws Exception { try (TestClass c = TestClass.create()) { c.example(); } }
留言列表