# try-with-resources

在块中可以声明一个或多个资源。程序完成后,会自动释放该资源。实现了 java.lang.AutoCloseable(包括实现 java.io.Closeable 的所有对象)可以用来声明。

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}
1
2
3
4
5
6

在该示例中生命的资源是 BufferedReader,声明语句出现在 try 后的括号中。 BufferedReader 在 jdk7+ 实现了 java.lang.AutoCloseable 接口。 因此可以在 try-with-resource 语句中声明,无论 try 语句是正常完成还是意外完成(BufferedReader.readLine 抛出 IOException),它都将被关闭。

在 jdk7 之前,可以使用 finally 块来确保资源是否关闭。

在 finally 块来确保资源是否关闭的话。try 块中出现异常会被抛出,并需要你捕获。 而使用 try-with-resource 语句,则异常会被屏蔽,可以不捕获异常,但是必须要抛出。 在 JDK7+ 版本中,可以检索屏蔽异常。请参阅 屏蔽异常 一节。

以下是声明多个资源,使用 try 同样需要你捕获

 @Test
    public void test() throws IOException {
        writeToFileZipFileContents("xxx.zip","xx.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);

        // 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());
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

多个声明使用分号隔开,代码块终止时,无论是正常还是异常,将按照此顺序自动调用对象的 close 方法。 注意,资源的 close 方法与他们创建相反的顺序调用。

try-with-resources 和捕获异常示例

public static void viewTable(Connection con) throws SQLException {

    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";

    try (Statement stmt = con.createStatement()) {
        ResultSet rs = stmt.executeQuery(query);

        while (rs.next()) {
            String coffeeName = rs.getString("COF_NAME");
            int supplierID = rs.getInt("SUP_ID");
            float price = rs.getFloat("PRICE");
            int sales = rs.getInt("SALES");
            int total = rs.getInt("TOTAL");

            System.out.println(coffeeName + ", " + supplierID + ", " +
                               price + ", " + sales + ", " + total);
        }
    } catch (SQLException e) {
        JDBCTutorialUtilities.printSQLException(e);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 屏蔽异常

可通过 Throwable[] getSuppressed() 获得。添加的话用 addSuppressed(Throwable exception),这个函数一般是在 try-with-resources 语句中自动调用的。

下面是上面的示例被编译成的 class 文件。可以看到 try-with-resources 也就是一个语法糖被编译后还是使用 try、catch、finally 来处理的。最后抛出一个异常。

仔细看只有 finally 中关闭资源的异常被屏蔽了。var5.addSuppressed(var32); 而且也能获得屏蔽的异常列表。这样的话,就能解决又不丢失异常信息,又能捕获且传递被屏蔽的异常了。

    public static void writeToFileZipFileContents(String zipFileName, String outputFileName) {
        Charset charset = StandardCharsets.US_ASCII;
        Path outputFilePath = Paths.get(outputFileName, new String[0]);

        try {
            ZipFile e = new ZipFile(zipFileName);
            Throwable var5 = null;

            try {
                BufferedWriter writer = Files.newBufferedWriter(outputFilePath, charset, new OpenOption[0]);
                Throwable var7 = null;

                try {
                    Enumeration entries = e.entries();

                    while (entries.hasMoreElements()) {
                        String newLine = System.getProperty("line.separator");
                        String zipEntryName = ((ZipEntry) entries.nextElement()).getName() + newLine;
                        writer.write(zipEntryName, 0, zipEntryName.length());
                    }
                } catch (Throwable var34) {
                    var7 = var34;
                    throw var34;
                } finally {
                    if (writer != null) {
                        if (var7 != null) {
                            try {
                                writer.close();
                            } catch (Throwable var33) {
                                var7.addSuppressed(var33);
                            }
                        } else {
                            writer.close();
                        }
                    }

                }
            } catch (Throwable var36) {
                var5 = var36;
                throw var36;
            } finally {
                if (e != null) {
                    if (var5 != null) {
                        try {
                            e.close();
                        } catch (Throwable var32) {
                            var5.addSuppressed(var32);
                        }
                    } else {
                        e.close();
                    }
                }

            }
        } catch (IOException var38) {
            (new Throwable(var38)).addSuppressed(var38);
        }

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 实现 AutoCloseable 或 Closeable 接口的类

有关实现这些接口的类的列表,请参阅 AutoCloseable 和 Closeable 接口的 Javadoc 。

public interface AutoCloseable {
     void close() throws Exception;
}
public interface Closeable extends AutoCloseable {
    public void close() throws IOException;
}
1
2
3
4
5
6

Closeable 扩展 AutoCloseable 接口,并且复写了 close 方法。抛出了一个具体的 IO 异常。