在 Java 7 之前,程序中如果有需要關閉的資源,例如 java.io.InputStream、java.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 查詢數據庫時,會依次創建 Connection、Statment、ResultSet,並且這三個資源都需要關閉,那麼可以這樣寫:
try (Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT ...")) {
// ...
} catch (Exception e) {
// ...
}
多個 resources 的關閉順序
如果在 try 中定義了多個 resources,那麼它們關閉的順序和創建的順序是相反的。上面的例子中,依次創建了 Connection、Statment、ResultSet 對象,最終關閉時會依次關閉 ResultSet、Statment、Connection,所以不用擔心 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 接口呢?
Closeable 在 java.io 包下,主要用於 IO 相關的資源的關閉,其 close() 方法定義了拋出 IOException 異常。其實現類實現 close() 方法時,不允許拋出除 IOException、 RuntimeException 外其他類型的異常。
而 AutoCloseable 位於 java.lang 包下,使用更廣泛。其 close() 方法定義是 void close() throws Exception,也就是它的實現類的 close() 方法對異常拋出是沒有限制的。
參考文檔
- https://docs.oracle.com/javas...