Java 之 try-with-resources

Java中打开的某些资源需要我们在使用完成后主动关闭,譬如输入输出流:

final InputStream in = con.getInputStream();
final FileOutputStream os = new FileOutputStream(file);

final byte[] buf = new byte[1024 * 8];

int len;

while (-1 != (len = in.read(buf))) {
    os.write(buf, 0, len);
}

os.close();
in.close();

但是我们有时候忘了主动关闭,或者逻辑有疏漏导致无法关闭,譬如以上代码在os.close()调用时抛出异常,就会导致in.close()无法执行到。

try-with-resources语句

Java7中新增了try-with-resources语句,提供了资源关闭的新方法。

首先来介绍下资源(resource)的含义;一个资源即一个在程序完成后必须被关闭的对象(object)。

try-with-resources语句就是声明了一个或多个资源的try语句,声明资源的语句放到try关键字后的圆括号()内,多个资源的声明以分号;分隔。

譬如下面就是声明了两个资源的try-with-resources语句:

try (
        final InputStream in = con.getInputStream();
        final FileOutputStream os = new FileOutputStream(patchFile)
) {

    ...
}

那么什么样的对象才能作为资源(resource)声明在try-with-resources语句中呢?

java.lang.AutoCloseable

Java7中新增了接口java.lang.AutoCloseable

package java.lang;

/**
 * @since 1.7
 */
public interface AutoCloseable {

    void close() throws Exception;
}

所有实现了(implements)java.lang.AutoCloseable接口的类的实例,都可以作为可关闭的资源(resource)try-with-resources语句中声明。这也包括所有实现了java.io.Closeable接口的对象,因为在Java7java.io.Closeable接口继承了java.lang.AutoCloseable接口:

package java.io;

import java.io.IOException;

/**
 * @since 1.5
 */
public interface Closeable extends AutoCloseable {

    public void close() throws IOException;
}

关闭资源

Java7以前,我们需要使用finally语句块来保证try语句正常执行完成或异常中止时,资源都能够正确的关闭。

如下代码从文件中读取一行文本,使用了BufferedReader对象的实例来从文件中读取数据,这个对象在程序执行完后必须关闭:

static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

而使用try-with-resources语句,简化为如下:

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

Java7中,BufferedReader实现了java.lang.AutoCloseable接口。将BufferedReader实例的声明放到try-with-resources语句里,当程序正常执行完或异常中止退出执行,BufferedReader实例都会被自动关闭。

多个资源关闭顺序

try-with-resources语句可以声明一个或多个资源。下面的例子从zip文件中获取打包的文件的名称并创建一个文本文件保存压缩包中的文件名称:

public static void writeToFileZipFileContents(String zipFileName,
                                           String outputFileName)
                                           throws java.io.IOException {

    java.nio.charset.Charset charset = java.nio.charset.StandardCharsets.US_ASCII;
    java.nio.file.Path outputFilePath = java.nio.file.Paths.get(outputFileName);

    try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}

这个例子中的try-with-resources语句中顺序声明了ZipFileBufferedWriter的实例,当程序正常或异常执行完成后,资源按照其声明顺序的逆序被依次关闭,即先关闭BufferedWriter的实例ZipFile,再关闭ZipFile实例。

Suppressed Exceptions

上面读取文件一行文本的代码,如果readLine和资源关闭的close方法都抛出了异常,两种方式抛出的异常不同。

finally语句块

readFirstLineFromFileWithFinallyBlock函数将抛出finally语句块内抛出的异常,try语句块抛出的异常则被抑制(suppressed)了。

try-with-resources语句

相反,如果readFirstLineFromFile函数在try语句块与try-with-resources语句中分别抛出了异常,函数会抛出try语句块抛出的异常,而try-with-resources语句中抛出的异常则被抑制(suppressed)了。

其中,try语句块中可以抛出一个异常,而try-with-resources语句中可以抛出一个或多个异常。

譬如上面读取zip压缩包中文件名称的writeToFileZipFileContents函数中,try语句块中可以抛出一个异常,而try-with-resources语句中可以抛出最多两个异常,即ZipFileBufferedWriter实例分别关闭抛出的异常。

try-with-resources语句中抛出的异常被try语句块中抛出的异常抑制(suppressed)时,可以使用try语句块中抛出的异常对象调用Throwable.getSuppressed方法来获取被抑制(suppressed)的异常:

package java.lang;
import  java.io.*;
import  java.util.*;

public class Throwable implements Serializable {

    /**
     * Returns an array containing all of the exceptions that were
     * suppressed, typically by the {@code try}-with-resources
     * statement, in order to deliver this exception.
     *
     * If no exceptions were suppressed or {@linkplain
     * #Throwable(String, Throwable, boolean, boolean) suppression is
     * disabled}, an empty array is returned.  This method is
     * thread-safe.  Writes to the returned array do not affect future
     * calls to this method.
     *
     * @return an array containing all of the exceptions that were
     *         suppressed to deliver this exception.
     * @since 1.7
     */
    public final synchronized Throwable[] getSuppressed() {
        ...
    }
}

参考阅读

The try-with-resources Statement



—  我的个人空间 |   —