【问题标题】:What is the reason for this finally clause containing close() calls这个 finally 子句包含 close() 调用的原因是什么
【发布时间】:2014-05-01 19:58:01
【问题描述】:

我正在学习在线 java 课程,Introduction to programming Using Java

在 I/O 章节中,下面的代码是用以下语句介绍的:

顺便说一句,在这个程序的最后,你会发现我们第一个有用的例子,即在 try 语句中的 finally 子句。当计算机执行 try 语句时,其 finally 子句中的命令无论如何都会保证执行。

该程序位于11.2.1 部分的末尾,是一个简单的程序,它只是从文件中读取一些数字并以相反的顺序写入它们。

main方法中的相关代码是(data是Reader,result是Writer):

try {
    // Read numbers from the input file, adding them to the ArrayList.        
    while ( data.eof() == false ) {  // Read until end-of-file.
        double inputNumber = data.getlnDouble();
        numbers.add( inputNumber );
    }

    // Output the numbers in reverse order.        
    for (int i = numbers.size()-1; i >= 0; i--)
        result.println(numbers.get(i));

    System.out.println("Done!");        
} catch (IOException e) {
    // Some problem reading the data from the input file.
    System.out.println("Input Error: " + e.getMessage());
} finally {
    // Finish by closing the files, whatever else may have happened.
    data.close();
    result.close();
}

所以我想知道当 try 或 catch 子句没有其他退出点时,为什么 finally 子句在这种情况下很有用。 close 方法不能只是在 main 的主体中吗?

我想这可能是因为理论上可能存在一些其他 RuntimeException 可能会使程序崩溃,然后让 Reader & Writers 处于未关闭状态,但是程序崩溃的事实不会关闭它们吗?

【问题讨论】:

  • 理论上是可能的,但这不是“保证无论如何都会被执行”。
  • “无论如何”有点高估了;)
  • 在现实生活中,程序代码往往会随着时间的推移而发展。假设您已经证明(一项也需要一些资源的任务!)此时没有可能抛出其他类型的异常,因此没有其他退出点。然而,总有一天你会改变一些东西,代码会开始表现不同。因此,添加一些结构以防止自己落入陷阱是明智的。这正是所有良好实践的意义所在。唯一的例外是,如果它是一个没有未来的一次性快速和肮脏的实用程序。
  • 可能是 try 块中的异常不一定会导致它崩溃,因为异常可能已在其自己的 catch 子句中或其他地方捕获(方法调用者可能已将其包装也在尝试中)。关键是您不能依赖程序崩溃并为您清理资源。

标签: java exception-handling try-catch-finally


【解决方案1】:

在抛出或不抛出异常的情况下,finally 子句将确保 dataresult 流都关闭。否则,它们可能不会关闭。

【讨论】:

    【解决方案2】:

    来自 Java 文档:

    finally 块总是在 try 块退出时执行。这可以确保即使发生意外异常也会执行 finally 块。但是 finally 不仅仅对异常处理有用——它允许程序员避免清理代码被 return、continue 或 break 意外绕过。将清理代码放在 finally 块中始终是一种很好的做法,即使没有预料到异常也是如此。

    关于您对以下方面的担忧:

    程序崩溃的事实无论如何都要关闭它们。

    资源分配在OS级别,而不是你的程序,所以如果你的程序没有机会清理,那么资源将被分配而没有真正使用。

    【讨论】:

      【解决方案3】:

      这是出于一个非常简单的原因:这是最安全的方式,在 Java 7 之前和 try-with-resources 中,即使捕获到异常,也能保证关闭您的资源。

      考虑一下如果你这样做了会发生什么:

      try {
          // some code, then
      
          resource.close();
      } catch (SomeException e) {
          // etc
      }
      

      如果您的SomeException 在资源关闭之前被抛出,您可能会泄漏资源。另一方面,将resource.close() 放入finally 可以保证无论发生什么其他情况它都会关闭。

      在 Java 7 中,您可以这样使用:

      try (
          final InputStream in = Files.newInputStream(Paths.get("somefile"));
          // Others
      ) {
          // work with "in" and others
      } catch (Whatever e) {
      }
      

      您的资源将在之前 catch 关闭。


      附带说明,使用 Java 6,关闭资源的最安全方法是使用 Guava 的 Closer

      【讨论】:

      • 最好用一些实际资源替换 // initialize resources 评论以显示变量分配是如何完成的
      • 但是如果你把 resource.close() 放在 try/catch 块之后有什么区别呢?这就是提问者想知道的区别。
      【解决方案4】:

      没错。如果RuntimeException 发生,finally 将被执行从而关闭资源,理论上这不是。这是一个实际的场景。

      此外,即使出现IOException(或您抓到的许多其他人)。 finally 子句阻止您编写相同的代码来多次关闭。

      【讨论】:

        【解决方案5】:

        你的想法是对的:finally块即使发生意外异常也会关闭资源。

        如果这样的异常使整个应用程序崩溃,这也是无关紧要的,但是通过查看此代码,您无法确定是否是这种情况。可能还有其他异常处理程序来捕获该异常,因此将关闭逻辑放在 finally 块中是一种很好且正确的做法。

        请注意,仍然可能隐藏了一个错误:如果data.close() 抛出异常,result.close() 将永远不会被调用。

        根据您的环境,修复错误的方法有多种。

        • 在 java 7 ff 中,您可以使用 try-with-resources

        • 如果您使用的是 Spring,则可能有一个类似于 JdbcTemplate 的适当模板

        • 如果这些都不适用,是的,您必须在 finally 中执行 try/finally。别丑了您绝对应该至少将其提取到 cmets 中建议的方法中。

        • 在 java pre 8 中概念上更简洁但相当冗长的是实现loan pattern。如果您碰巧没有与 scala/clojure/haskell 开发人员合作,那可能会比其他任何事情都更令人困惑。

        【讨论】:

        • 修复您提到的错误的最佳方法是什么?您是否建议在 finally 块中尝试..finally?
        • @Cruncher 我通常会编写一个closeQuietly(Closeable) 方法来捕获并记录发生的任何异常
        • @NickHolt 多么了不起的coincidence,但我认为我的更笼统。
        • 另外,一个程序可以有多个线程,并且在异常情况下“崩溃”其中一个并不会终止程序。
        【解决方案6】:

        这实际上是您形成的正确概念。当出现CheckedException 之类的IOException 时,必须关闭所有资源。这是因为:

        1. 当您执行程序时,资源正在 访问。假设你改变了一些东西而你没有 保存它们,您将不会获得更新的文件。 (这发生在 您使用缓冲区 - 它们不会立即写入数据,它们 写成)。

        2. 由于文件在 JVM 中打开,它有可能保持打开状态,您需要关闭应用程序 使用。因此,您需要close() 资源,以便 缓冲区被刷新,即保存更改。

        例如:

        try {
            BufferedReader br = new BufferredReader (new FileReader("Example.txt"));
        
            ArrayList<String> lines = new ArrayList<>();
            String line;
            while ( (line = br.readLine()) != null ) {
                lines.add(line);
            }
        catch (IOException ie) {
            //Error handling.
        } finally {
            br.close();
        }
        

        编辑:随着JDK1.7的出现,现在可以使用try with resources,如下图:

        try (BufferedReader br = new BufferredReader (new FileReader("Example.txt"));) {
            ArrayList<String> lines = new ArrayList<>();
            String line;
            while ( (line = br.readLine()) != null ) {
                lines.add(line);
            }
        catch (IOException ie) {
            //Error handling.
        }
        

        现在,不需要 finally 块,因为 BufferedReader 实现 AutoCloseable。顾名思义,当try (..) 块离开时,它会自动关闭缓冲区。

        【讨论】:

        • IOException 不是 RuntimeException,而是已检查的异常。
        【解决方案7】:

        如果程序在那之后终止,那么是的,这也会关闭 I/O 资源。

        但许多程序不会终止。有些必须运行 24/7 多年。所以正确清理你的资源是必须的。

        不幸的是,Java try-with-resource statement”,它会试图堵住这个漏洞。

        除非您可以使用此版本,否则您必须自己进行清理。

        也就是说,上面的代码仍然有问题:close() 本身可以抛出异常,因此一些 I/O 资源可能仍然存在。您应该改用IOUtils.closeQuietly() 之类的工具:

        Reader reader = null;
        try {
            reader = ...open...
        
            ...use reader...
        } finally {
            IOUtils.closeQuietly(reader);
        }
        

        【讨论】:

          【解决方案8】:

          最终确保始终调用一段代码。因此,它是关闭连接的最佳位置。我建议您也将 close 语句放在 try catch 中,因为 finally 块中可能仍然会出现问题:

          finally {
              if (data != null) try { data.close() } catch(exception ex) { ex.printstacktrace() }
          }
          

          【讨论】:

            【解决方案9】:

            “关闭”方法可以做的不仅仅是通知操作系统不再需要某个资源。其中,封装了各种形式的数据流的对象本身可能会缓存一定量的数据,并在它们关闭时或在积累了一定量的数据时将其传递给操作系统。拥有一个封装日志数据流的对象以大块的形式将数据传递给操作系统可能比让它单独将日志事件传递给操作系统效率更高,但是如果程序在没有“关闭”日志文件的情况下死掉,那么可能与任何诊断该问题的人都非常相关。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-08-05
              • 2016-08-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-07-30
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多