try-with-resources语句

try-with-resources语句是一种声明了一种或多种资源的try语句。资源是指在程序用完了之后必须要关闭的对象。try-with-resources语句保证了每个声明了的资源在语句结束的时候都会被关闭。任何实现了java.lang.AutoCloseable接口的对象,和实现了java.io.Closeable接口的对象,都可以当做资源使用。

新结构扩展了 try 块,按照与 for 循环相似的方式声明了资源。在 try 块中声明打开的任何资源都会关闭。因此,这个新结构使您不必配对使用 try 块与对应的 finally 块,后者专用于正确的资源管理。使用分号分隔每个资源

下面的例子读取了一个文件的第一行。它使用了一个BufferedReader实例去读取文件,BufferedReader是一种资源,用完之后必须关闭。


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

在这个例子中,try-with-resources语句种声明的是BufferedReader资源。声明语句紧跟着在try关键词的圆括号里面。BufferedReader从Java SE7开始就实现了java.lang.AutoCloseable接口。因为BufferedReader声明在了try-with-resources里面,所以无论try语句是正常结束还是异常结束(比方说BufferedReader.readLine方法抛出一个IOException异常),它都会被关闭。

在Java SE7之前,你可以用finally代码块来确保资源一定被关闭,无论try语句正常结束还是异常结束。下面的例子用finally代码块代替try-with-resources语句:


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

然而,在这个例子中,如果readLine和close方法都抛出异常,那么readFirstLineFromFileWithFinallyBlock方法最终会在finally代码块抛出异常,从try代码块里面抛出的那个异常被抑制住了。相比之下,readFirstLineFromFile方法中,如果try代码块和try-with-resources语句同时抛出异常,这个方法将会最终抛出try代码块里面的异常,try-with-resources语句里面抛出的异常被压抑了。在Java SE7之后,你可以找回被压抑的异常。参看后面的“被压抑的异常”部分。

try-with-resources语句里面声明多个资源

下面的例子展示了从一个名字为zipFileName的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());
        }
    }
}

在这个例子中,try-with-resources语句包含了两个用分号隔开的声明:ZipFile和BufferedWriter。当代码块中代码终止,不管是正常还是异常,BufferedWriter和ZipFile对象的close方法都会自动按声明的相反顺序调用。

下面的例子用try-with-resources语句自动关闭一个java.sql.Statement对象:


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

例子中的资源java.sql.Statement是JDBC 4.1及其后面版本的一部分。

注意:try-with-resources语句也可以像普通的try语句一样,有catch和finally代码块。在try-with-resources语句中,任何的catch和finally代码块都在所有被声明的资源被关闭后执行。

被压抑的异常

try-with-resources语句相关联的代码块可能会抛出异常。在writeToFileZipFileContents例子中,try代码块中可能会抛出异常,并且有高达两个异常可能会在try-with-resources语句抛出,当试图去关闭ZipFile和BufferedWriter对象的时候。如果在try代码块中抛出一个异常,同时在try-with-resources语句中抛出一个或多个异常,那么在try-with-resources语句中抛出的异常就被压抑了,并且最终在writeToFileZipFileContents方法抛出的异常就是try代码块中抛出的那个异常。你可以通过被try代码块抛出的异常的Throwable.getSuppressed方法找回被压抑的异常。

实现了AutoCloseable或Closeable接口的类

看 AutoCloseable 和 Closeable 接口的javadoc,看看实现了它们的类都有哪些。Closeable接口继承了AutoCloseable接口。不同之处在于,Closeable接口的close方法抛出的是IOException异常,AutoCloseable接口的close方法抛出的是Exception异常。 因此,AutoCloseable的子类可以重写close方法的行为,抛出指定的异常,比如IOException异常,甚至可以不抛出异常。强烈建议,实现的类和子接口应声明一种比 java.lang.Exception 更精确的异常类型,当然,更好的情况是,如果调用 close() 方法不会导致失败,就根本不用声明异常类型。

深入理解try-with-resources语法

下面模拟实现资源调用work(),模拟自动关闭方法close(),然后分别主动抛出异常,来看看新语法是如何处理被抑制的异常的情况。


public class TestResources {

    public static void main(String args[]) throws MyException {
           try (AutoClose autoClose = new AutoClose()) {
               autoClose.work();
           }
       }
}

public class AutoClose implements AutoCloseable {    

    @Override
    public void close() {
        System.out.println(">>> close()");
        throw new RuntimeException("Exception in close()");
    }

    public void work() throws MyException {
        System.out.println(">>> work()");
        throw new MyException("Exception in work()");
    }
}

public class MyException extends Exception {

    private static final long serialVersionUID = 1L;

    public MyException() {
        super();
    }

    public MyException(String message) {
        super(message);
    }
}

使用JD-GUI的Decompile工具进行反编译main后如下:


public class TestResources
{
  public static void main(String[] paramArrayOfString)
    throws MyException
  {
    AutoClose localAutoClose = new AutoClose();Object localObject1 = null;
    try
    {
      localAutoClose.work();
    }
    catch (Throwable localThrowable2)
    {
      localObject1 = localThrowable2;throw localThrowable2;
    }
    finally
    {
      if (localAutoClose != null) {
        if (localObject1 != null) {
          try
          {
            localAutoClose.close();
          }
          catch (Throwable localThrowable3)
          {
            ((Throwable)localObject1).addSuppressed(localThrowable3);
          }
        } else {
          localAutoClose.close();
        }
      }
    }
  }
}

编译器处理了可能为 null 的资源引用,在 finally 块中添加了额外的 if 语句来检查给定资源是否为 null,从而避免对 null 引用调用 close() 时的空指针异常。我们的手动实现中并未这样做,因为资源不可能为 null。但编译器会系统性地生成此类代码。

由于异常屏蔽在实际中是如此重要的一个问题,因此 Java SE 7 扩展了异常,这样就可以将“被抑制的”异常附加到主异常上。我们之前所说的“屏蔽的”异常实际上就是一个被抑制并附加到主异常的异常。

建议不要声明任何不应被抑制的异常,java.lang.InterruptedException 就是最好的例子。实际上,抑制该异常并将其附加到另一个异常可能会导致忽略线程中断事件,使应用程序处于不一致的状态。

try-with-resources 语句中的资源嵌套问题

上面模拟引用了一个资源,若有多个资源被引用,则try-with-resources语法将在编译的时候为每个资源分别用try包裹并实现类似上述的做法。

如:


private static void compress(String input, String output) throws IOException {
       try(
           FileInputStream fin = new FileInputStream(input);
           FileOutputStream fout = new FileOutputStream(output);
           GZIPOutputStream out = new GZIPOutputStream(fout)
       ) {
           byte[] buffer = new byte[4096];
           int nread = 0;
           while ((nread = fin.read(buffer)) != -1) {
               out.write(buffer, 0, nread);
           }
       }
   }

一般来说,一个 try 块声明的资源越多,所生成的代码也就越复杂。因此,可以生成更精简的异常处理块:


private static void compress(String input, String output) throws IOException {
       try(
           FileInputStream fin = new FileInputStream(input);     
           GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(output))
       ) {
           byte[] buffer = new byte[4096];
           int nread = 0;
           while ((nread = fin.read(buffer)) != -1) {
               out.write(buffer, 0, nread);
           }
       }
   }

强调
好的做法是在 try-with-resources 语句中为每一个持有关键系统资源(如文件描述符、套接字或者 JDBC 连接)的每个资源进行单独声明,必须确保 close() 方法最终得到调用。否则,如果相关资源 API 允许,选择链接分配就不仅是一种惯例:在防止资源泄漏的同时还能得到更为紧凑的代码。

结论

本文介绍了 Java SE 7 中一种新的用于安全管理资源的语言结构。这种扩展带来的影响不仅仅是更多的语法糖。事实上,它能位开发人员生成了正确的代码,消除了编写容易出错的样板代码的需要。更重要的是,这种变化还伴随着将一个异常附加到另一个异常的改进,从而为众所周知的异常彼此屏蔽问题提供了完善的解决方案。

来自:
官方介绍

相关博客:
懒癌正患者

  • Java 继承调用父类构造函数

    实例分析public class ParentTest { public ParentTest() { System.ou...

    Java 继承调用父类构造函数
  • IO流

    Java IO体系结构IO实际上分为阻塞型IO(Blocking IO)和非阻塞型IO(Non-Blocking IO 简称NIO) ...

    IO流
  • Java中的Enum关键字

    一般常量的定义方式 来自 Effective Java Edition 3 // The int enum pattern - s...

    Java中的Enum关键字