博客 / 詳情

返回

Java try-with-resources 中的幾個細節

在 Java 7 之前,程序中如果有需要關閉的資源,例如 java.io.InputStreamjava.sql.Connection 等,通常會在 finally 中關閉,例如:

InputStream inputStream = null;
try {
    inputStream = new FileInputStream("/my/file");
    // ...
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (inputStream != null) {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在 Java 7 以及後續版本中,支持 try-with-resources,任何實現 java.lang.AutoCloseable 接口的類,包括 java.io.Closeable 的實現類,都可以通過 try-with-resources 來關閉。

上面代碼通過 try-with-resources 可以簡化為:

try (InputStream inputStream = new FileInputStream("/my/file")) {
    // ...
} catch (Exception e) {
    e.printStackTrace();
}

支持定義多個 resources

通過 JDBC 查詢數據庫時,會依次創建 ConnectionStatmentResultSet,並且這三個資源都需要關閉,那麼可以這樣寫:

try (Connection connection = DriverManager.getConnection(url, user, password);
     Statement statement = connection.createStatement();
     ResultSet resultSet = statement.executeQuery("SELECT ...")) {
    // ...
} catch (Exception e) {
    // ...
}

多個 resources 的關閉順序

如果在 try 中定義了多個 resources,那麼它們關閉的順序和創建的順序是相反的。上面的例子中,依次創建了 ConnectionStatmentResultSet 對象,最終關閉時會依次關閉 ResultSetStatmentConnection,所以不用擔心 Connection 會先 close。

官方文檔:

Note that the close methods of resources are called in the opposite order of their creation.

catch、finally 和 close 的先後順序

官方文檔:

In a try-with-resources statement, any catch or finally block is run after the resources declared have been closed.

在 try-with-resources 中,catch 和 finally 中的代碼在資源關閉之後運行。

以下是一種錯誤寫法:

Connection connection = DriverManager.getConnection(url, user, password);
try (Connection connection2 = connection) {
    // ...
} catch (SQLException e) {
    e.printStackTrace();
} finally {
    try {
        Statement statement = connection.createStatement(); // 異常,此時 Connection 已關閉
        // ...
    } catch (SQLException ex) {
        ex.printStackTrace();
    }
}

創建和關閉 resources 時的異常處理

在 try-with-resources 中,如果創建資源發生異常,即 try (...) 中小括號裏的代碼出現異常,以及 close 時發生異常,都是會在 catch 中捕捉到的。例如:

try (InputStream inputStream = new FileInputStream("/not/exist/file")) {
    // ...
} catch (Exception e) {
    e.printStackTrace(); // 如果文件不存在,這裏會捕獲異常
}

這段代碼中,如果 new FileInputStream("/not/exist/file") 對應的文件不存在,就會拋出異常,這個異常會在下面的 catch 中捕獲到。

Suppressed Exceptions

在 try-with-resources 中,如果 try block(即 try 後面大括號中的代碼)拋出異常,會觸發資源的 close,如果此時 close 也發生了異常,那麼 catch 中會捕獲到哪一個呢?

由於 close 拋出異常不是很常見,所以自己實現一個 AutoCloseable 實現類:

public class MyResource implements AutoCloseable {

    public void doSomething() throws Exception {
        throw new Exception("doSomething exception");
    }

    @Override
    public void close() throws Exception {
        throw new Exception("close exception");
    }
}

測試代碼:

public class Test {

    public static void main(String[] args) {
        try (MyResource myResource = new MyResource()) {
            myResource.doSomething();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運行結果:

java.lang.Exception: doSomething exception
    at com.xxg.MyResource.doSomething(MyResource.java:6)
    at com.xxg.Test.main(Test.java:12)
    Suppressed: java.lang.Exception: close exception
        at com.xxg.MyResource.close(MyResource.java:11)
        at com.xxg.Test.main(Test.java:13)

可以看到 catch 中捕獲的是 doSomething() 方法拋出的異常,同時這個異常會包含一個 Suppressed Exception,即 close() 方法拋出的異常。

如果想直接拿到 close() 方法拋出的異常,可以通過 Throwable.getSuppressed() 方法獲取:

try (MyResource myResource = new MyResource()) {
    myResource.doSomething();
} catch (Exception e) {
    // e 是 doSomething 拋出的異常,想要拿到 close 方法拋出的異常需要通過 e.getSuppressed 方法獲取
    Throwable[] suppressedExceptions = e.getSuppressed();
    Throwable closeException = suppressedExceptions[0];
    closeException.printStackTrace();
}

Closeable 和 AutoCloseable

AutoCloseable 是 Java 7 新增的接口,Closeable 早就有了。二者的關係是 Closeable extends AutoCloseable。二者都僅包含一個 close() 方法。那麼為什麼 Java 7 還要新增 AutoCloseable 接口呢?

Closeablejava.io 包下,主要用於 IO 相關的資源的關閉,其 close() 方法定義了拋出 IOException 異常。其實現類實現 close() 方法時,不允許拋出除 IOExceptionRuntimeException 外其他類型的異常。

AutoCloseable 位於 java.lang 包下,使用更廣泛。其 close() 方法定義是 void close() throws Exception,也就是它的實現類的 close() 方法對異常拋出是沒有限制的。

參考文檔

  • https://docs.oracle.com/javas...

關注我

掃碼關注

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.