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();
    }
}

 

arrow
arrow
    全站熱搜

    wylokgo101 發表在 痞客邦 留言(0) 人氣()