使用AutoCloseable(try-with-resources)关闭多个资源

82

我知道如果你在try语句中传递的资源实现了AutoCloseable接口,它将自动关闭。目前为止还不错。但是当我有多个需要自动关闭的资源时该怎么办?比如使用sockets的示例;

try (Socket socket = new Socket()) {
    input = new DataInputStream(socket.getInputStream());
    output = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
} 

所以我知道套接字会被正确关闭,因为它作为参数传递给try语句,但是输入和输出应该如何正确关闭?

4个回答

96

使用try-with-resources可以通过在括号中声明它们来同时使用多个资源。请参见文档

链接文档的相关代码摘录:

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);

    // Open zip file and create output file with 
    // try-with-resources statement

    try (
        java.util.zip.ZipFile zf =
             new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = 
            java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        // Enumerate each entry
        for (java.util.Enumeration entries =
                                zf.entries();     entries.hasMoreElements();) {
            // Get the entry name and write it to the output file
            String newLine = System.getProperty("line.separator");
            String zipEntryName =
                 ((java.util.zip.ZipEntry)entries.nextElement()).getName() 
             newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }
}
如果您的对象没有实现 AutoClosable 接口(DataInputStream 实现了),或者必须在 try-with-resources 之前声明,那么关闭它们的适当位置是在一个 finally 块中,这也在链接的文档中提到过。

如果您的对象没有实现 AutoClosable 接口(DataInputStream 实现了),或者必须在 try-with-resources 之前声明,那么关闭它们的适当位置是在一个 finally 块中,这也在链接的文档中提到过。


31

别担心,事情会“自动运行”。来自Socket的文档:

关闭此套接字还将关闭套接字的InputStream和OutputStream。

我理解您有关不显式调用输入和输出对象的close()的顾虑,实际上通常最好确保所有资源都由try-with-resources块自动管理,像这样:

try (Socket socket = new Socket();
     InputStream input = new DataInputStream(socket.getInputStream());
     OutputStream output = new DataOutputStream(socket.getOutputStream());) {
} catch (IOException e) {
} 

这将导致socket对象被“多次关闭”,但这不应该造成任何损害(这也是通常建议所有close()的实现都要做到幂等性的原因之一)。


3
只有在没有缓冲的情况下才能保证数据不会丢失。如果使用了BufferedOutputStream,且未执行flush操作,那么数据将会丢失,因为在关闭socket输出流之前并没有将数据写出。 - Silly Freak

10
除了以上的回答,这是Java 9中新增的改进内容。
Java 9的try-with-resources提供了一种更好的编写代码的方式。现在你可以在try块外部声明变量,并直接在try块内使用它们。由于这个改进,你将获得以下好处。
- 在try块外部声明的资源(有效地是final或final)可以通过将它们添加到try块中的自动资源管理来自动关闭。 - 你不需要重新引用在try块外部声明的对象,也不需要像在Java 7中那样手动关闭它们。 - 它还有助于编写清晰的代码。
在Java 9中,我们可以这样编写try-with-resource。
public void loadDataFromDB() throws SQLException {
Connection dbCon = DriverManager.getConnection("url", "user", "password");
try (dbCon; ResultSet rs = dbCon.createStatement().executeQuery("select * from emp")) {
    while (rs.next()) {
        System.out.println("In loadDataFromDB() =====>>>>>>>>>>>> " + rs.getString(1));
    }
} catch (SQLException e) {
    System.out.println("Exception occurs while reading the data from DB ->" + e.getMessage());
}

在这里,自动资源管理将自动关闭对象dbCon和rs。

为了更好地理解上述定义用例的列表,请查找一些Java 7代码。

示例1:

public void loadDataFromDB() throws SQLException {
Connection dbCon = DriverManager.getConnection("url", "user", "password");
try (ResultSet rs = dbCon.createStatement().executeQuery("select * from emp")) {
    while (rs.next()) {
        System.out.println("In loadDataFromDB() =====>>>>>>>>>>>> " + rs.getString(1));
    }
} catch (SQLException e) {
    System.out.println("Exception occurs while reading the data from DB ->" + e.getMessage());
} finally {
    if (null != dbCon)
        dbCon.close();
}

示例2:

// BufferedReader is declared outside try() block
    BufferedReader br = new BufferedReader(new FileReader("C://readfile/input.txt"));

    try (BufferedReader inBr = br) {
            // ...
        }
    } catch (IOException e) {
        // ...
    }

在上面的示例中,您可以看到如果对象在try块之外,那么我们需要手动关闭或重新引用它。而且,在try块中有多个对象的情况下,这看起来很乱,即使您在try块内声明了它,也不能在try块外使用它。

1
在第一个代码片段中,try-with-resources语句会关闭所有三个对象:Connection,Statement和ResultSet吗?我相当确定它只会关闭Connection和ResultSet,并且依靠关闭Connection来关闭Statement。 - Mason T.

2

以上的答案都很好,但在某些情况下,try-with-resources是无法帮助的。

看一下这个代码示例:

private static byte[] getFileBytes(Collection<String> fileContent) throws CustomServiceException {
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos))) {
            for (String fileLine : fileContent) {
                writer.append(fileLine);
                writer.newLine();
            }
        }
        return baos.toByteArray();
    } catch (IOException e) {
        throw new CustomServiceException(SC_INTERNAL_SERVER_ERROR, "Unable to serialize file data.");
    }
}

在这个例子中,你不能只使用try-with-resources块,因为写入器必须刷新输出缓冲区到底层字符流,所以把写入器放在try-with-resources块中不起作用,该方法将返回空数组。

ByteArrayOutputStream.close() 是一个空操作,而 BufferedWriter.close() 则会刷新 BufferedWriter。因此,您不需要在 try-with-resources 中声明 baos,尽管我不认为这样做有什么坏处。以下代码应该可以工作(为简洁起见省略了异常处理): ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(baos))) { for (String fileLine : fileContent) { writer.append(fileLine); writer.newLine(); } } return baos.toByteArray(); - Dan Breslau
你可以在返回之前手动刷新。话虽如此,这不是问题的答案,而是一条评论... - vadipp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接