Effective Java 第三版读书笔记——条款9:使用 try-with-resources 语句替代 try-finally 语句

Java 类库中包含许多必须手动调用 close 方法来关闭的资源, 比如InputStreamOutputStreamjava.sql.Connection

从以往来看,try-finally 语句是保证资源正确关闭的最佳方式,即使是在程序抛出异常或返回的情况下:

// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}

这看起来并不差,但是当添加第二个资源时,情况会变得很糟:

// try-finally is ugly when used with more than one resource!
static void copy(String src, String dst) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dst);
        try {
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}

即使是用 try-finally 语句关闭资源的正确代码(如前面两个代码示例所示)也有一个微妙的缺陷。 try 块和 finally 块中的代码都可以抛出异常。 例如,在 firstLineOfFile 方法中,由于底层物理设备发生故障,对 readLine 方法的调用可能会引发异常,并且由于相同的原因,调用 close 方法可能也会失败。 在这种情况下,第二个异常完全冲掉了第一个异常。 在异常堆栈跟踪中没有第一个异常的记录,这可能使实际系统中的调试变得非常复杂——通常你想要看到第一个异常来诊断问题。 虽然可以编写代码来抑制第二个异常,但是实际上没有人这样做,因为它太冗长了。

当 Java 7 引入了 try-with-resources 语句时,所有这些问题都得到了解决。要使用这个构造,资源必须实现 AutoCloseable 接口,该接口由一个返回类型为 voidclose 方法组成。Java 类库和第三方类库中的许多类和接口现在都实现或继承了 AutoCloseable 接口。如果你编写的类表示必须关闭的资源,那么这个类也应该实现 AutoCloseable 接口。

下面是第一个使用 try-with-resources 语句的示例:

// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(
           new FileReader(path))) {
       return br.readLine();
    }
}

下面是第二个使用 try-with-resources 语句的示例:

// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
    try (InputStream   in = new FileInputStream(src);
         OutputStream out = new FileOutputStream(dst)) {
        byte[] buf = new byte[BUFFER_SIZE];
        int n;
        while ((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);
    }
}

try-with-resources 版本比原始版本更精简,具有更好的可读性,而且提供了更好的可诊断性。 考虑 firstLineOfFile 方法。 如果调用 readLine 方法和(不可见的)close 方法都抛出异常,则后一个异常将被抑制(suppressed),而不是前者。 事实上,为了保留你真正想看到的异常,可能会有多个异常被抑制。 这些被抑制的异常没有被抛弃,而是打印在堆栈跟踪中,并标注为被抑制了。你也可以使用 getSuppressed 方法在程序中访问它们,该方法在 Java 7 被添加到 Throwable 中。

还可以在 try-with-resources 语句中添加 catch 子句,就像在常规的 try-finally 语句中一样。这允许你处理异常,而不会用另一层嵌套污染代码。下面是一个稍微有些不自然的例子,它不会抛出异常,但是如果它不能打开或读取文件,则返回默认值:

// try-with-resources with a catch clause
static String firstLineOfFile(String path, String defaultVal) {
    try (BufferedReader br = new BufferedReader(
           new FileReader(path))) {
        return br.readLine();
    } catch (IOException e) {
        return defaultVal;
    }
}

结论明确:在处理必须关闭的资源时,使用 try-with-resources 语句替代 try-finally 语句。 这会使生成的代码更简洁、更清晰,并且抛出的异常在调试时更有用。

posted @ 2018-11-18 10:32  LeeFire  阅读(439)  评论(0编辑  收藏  举报