这个包含 close() 调用的 finally 子句的原因是什么?



我正在学习在线Java课程,使用Java编程简介。

在有关 I/O 的章节中,使用以下语句介绍了以下代码:

顺便说一下,在本程序结束时,您将在 try 语句中找到我们的第一个有用的 finally 子句示例。当计算机执行 try 语句时,无论如何,都可以保证执行其 finally 子句中的命令。

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

main 方法中的相关代码是(数据是读取器,结果是写入器(:

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

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

我想也许是因为理论上可能存在其他一些 RuntimeException,它可能会使程序崩溃,然后让阅读器和作家不关闭,但是程序崩溃的事实不会关闭它们吗?

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

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

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

根据您的环境,关于如何修复错误有多种风格。

  • 在Java 7 FF中,您可以使用Try-with-resources

  • 如果您使用的是 Spring,则可能会有一个类似于 JdbcTemplate 的正确模板。

  • 如果这些都不适用,是的,您必须尝试/最终进入最后。别再丑了。您绝对应该至少将其提取到注释中建议的方法中。

  • 在 Java pre 8 中,概念上更干净但相当冗长是实现贷款模式。如果你不碰巧与scala/clojure/haskell开发人员合作,它可能比其他任何事情都更令人困惑。

原因很简单:这是最安全的方法,在 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

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

但许多程序不会终止。有些必须 24/7 全天候运行多年。因此,正确清理资源是必须的。

不幸的是,Java <7 只提供了内存(垃圾回收(的自动清理机制。在Java 7中,你会得到新的"try-with-resource语句",试图堵住这个洞。

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

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

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

来自 Java 文档:

finally 块始终在 try 块退出时执行。这可确保即使发生意外异常,也会执行 finally 块。但最终不仅对异常处理有用 - 它允许程序员避免清理代码被返回,继续或中断意外绕过。将清理代码放在 finally 块中始终是一种很好的做法,即使预计不会出现异常也是如此。

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

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

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

True。如果发生RuntimeException,最终将被执行,从而关闭资源,这在理论上不是。这是一个实际场景。

此外,即使发生IOException(或您捕获的许多其他人(。 finally子句阻止您编写相同的代码进行多次关闭。

最后确保

始终调用一段代码。正因为如此,它是关闭连接的最佳场所。我建议也将您的关闭语句放在尝试捕获中,因为在 finally 块中可能仍然会出错:

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

这实际上是你形成的一个正确的概念。当发生类似IOException CheckedException时,必须关闭所有资源。这是因为:

  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 (..)它会自动关闭缓冲区。

在引发或未引发异常的情况下,finally 子句将确保关闭data流和result流。否则,它们可能不会关闭。

"close"方法不仅可以简单地通知操作系统不再需要资源。 除此之外,封装各种形式的数据流的对象可以自己缓冲一定数量的数据,并在它们关闭或积累一定数量的数据时将其传递给操作系统。 让一个封装日志数据流的对象将数据以大块的形式传递到操作系统可能比让它单独将日志事件传递给操作系统要有效得多,但如果程序在没有"关闭"日志文件的情况下死亡,则可能与诊断问题的任何人非常相关的信息将丢失。

最新更新