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

在 Java 7 以前,程序中若是有須要關閉的資源,例如 java.io.InputStreamjava.sql.Connection 等,一般會在 finally 中關閉,例如:html

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 來關閉。java

上面代碼經過 try-with-resources 能夠簡化爲:sql

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。segmentfault

官方文檔:oracle

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

catch、finally 和 close 的前後順序

官方文檔:ide

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 中的代碼在資源關閉以後運行。測試

如下是一種錯誤寫法:url

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 中捕捉到的。例如:spa

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() 方法對異常拋出是沒有限制的。

參考文檔

關注我

掃碼關注

相關文章
相關標籤/搜索