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() 方法对异常抛出是没有限制的。

参考文档

关注我

扫码关注

相关文章
相关标签/搜索