在try-with-resources块中管理多个链式资源的正确习惯用法?

Nat*_*tix 163 java try-with-resources

当仅使用一个资源时,Java 7 try-with-resources语法(也称为ARM块(自动资源管理))很好,简短而直接AutoCloseable.但是,当我需要声明彼此依赖的多个资源时,我不确定什么是正确的习惯用法,例如a FileWriter和a BufferedWriter包装它.当然,这个问题涉及AutoCloseable包装某些资源的任何情况,而不仅仅是这两个特定的类.

我提出了以下三种选择:

1)

我见过的天真的习惯是只声明ARM管理变量中的顶级包装器:

static void printToFile1(String text, File file) {
    try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}
Run Code Online (Sandbox Code Playgroud)

这很好而且简短,但它已被打破.由于底层FileWriter未在变量中声明,因此永远不会在生成的finally块中直接关闭它.它只能通过close包装方法关闭BufferedWriter.问题是,如果从bw构造函数抛出异常,close则不会调用它,因此底层FileWriter 将不会被关闭.

2)

static void printToFile2(String text, File file) {
    try (FileWriter fw = new FileWriter(file);
            BufferedWriter bw = new BufferedWriter(fw)) {
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}
Run Code Online (Sandbox Code Playgroud)

这里,底层资源和包装资源都是在ARM管理的变量中声明的,因此它们肯定会被关闭,但底层fw.close() 将被调用两次:不仅直接调用,还通过包装调用bw.close().

这对于这两个实现Closeable(它们是子类型AutoCloseable)的特定类来说应该不是问题,其合同规定close允许多次调用:

关闭此流并释放与其关联的所有系统资源.如果流已经关闭,则调用此方法无效.

但是,在一般情况下,我可以拥有仅实现AutoCloseable(而不是Closeable)的资源,但不能保证close可以多次调用:

请注意,与java.io.Closeable的close方法不同,此close方法不需要是幂等的.换句话说,不止一次调用此close方法可能会产生一些可见的副作用,这与Closeable.close不同,如果多次调用则需要它无效.但是,强烈建议强制使用此接口的实现者使其close方法具有幂等性.

3)

static void printToFile3(String text, File file) {
    try (FileWriter fw = new FileWriter(file)) {
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write(text);
    } catch (IOException ex) {
        // handle ex
    }
}
Run Code Online (Sandbox Code Playgroud)

这个版本在理论上应该是正确的,因为只有fw代表需要清理的真实资源.在bw本身不拥有任何资源,它只是委托给fw,所以它应该是足够的,只关闭基础fw.

另一方面,语法有点不规则,并且Eclipse发出警告,我认为这是一个误报,但它仍然是一个必须处理的警告:

资源泄漏:'bw'永远不会关闭


那么,采用哪种方法?或者我错过了其他一些正确的习语?

Tom*_*ine 74

这是我对替代方案的看法:

1)

try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
    bw.write(text);
}
Run Code Online (Sandbox Code Playgroud)

对我来说,15年前从传统C++到Java的最好的事情就是你可以相信你的程序.即使事情发生在他们经常做的事情中,我也希望其余的代码能够达到最好的行为和玫瑰的味道.实际上,这BufferedWriter可能会引发例外.例如,耗尽内存并不罕见.对于其他装饰器,您知道哪个java.io包装类从其构造函数中抛出一个已检查的异常吗?我不.如果你依赖那种晦涩的知识,那么代码的可理解性就不会很好.

还有"破坏".如果存在错误情况,那么您可能不希望将垃圾清除到需要删除的文件(未显示的代码).当然,删除文件也是另一个有趣的错误处理操作.

通常,您希望finally块尽可能短且可靠.添加刷新对此目标没有帮助.对于许多版本的一些在JDK缓冲类有一个地方从异常错误flushclose引起close装饰对象不被调用.虽然已经修复了一段时间,但期望从其他实现中获得.

2)

try (
    FileWriter fw = new FileWriter(file);
    BufferedWriter bw = new BufferedWriter(fw)
) {
    bw.write(text);
}
Run Code Online (Sandbox Code Playgroud)

我们仍在冲洗隐含的finally块(现在重复close- 当你添加更多装饰器时这会变得更糟),但构造是安全的,我们必须隐含finally块,所以即使失败flush也不会阻止资源释放.

3)

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
}
Run Code Online (Sandbox Code Playgroud)

这里有一个错误.应该:

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
    bw.flush();
}
Run Code Online (Sandbox Code Playgroud)

一些执行不佳的装饰器实际上是资源,需要可靠地关闭.还有一些流可能需要以特定方式关闭(可能它们正在进行压缩并需要写入位来完成,并且不能只是刷新所有内容.

判决书

虽然3是技术上优越的解决方案,但软件开发的原因使2成为更好的选择.然而,try-with-resource仍然是一个不充分的修复,你应该坚持使用Execute Around成语,它应该有一个更清晰的语法与Java SE 8中的闭包.

  • 你能给出一个"带闭合的执行周围成语"的例子吗? (8认同)
  • 在版本3中,您如何知道bw不需要接近它?即使你可以确定它是否也不像你提到的版本1那样"模糊不清"? (4认同)
  • "*软件开发的原因使得2更好的选择*"你能更详细地解释这个陈述吗? (3认同)
  • 你能解释一下"Java SE 8中关闭闭包的清晰语法"吗? (2认同)

Dan*_*ral 19

第一种风格是Oracle建议的风格.BufferedWriter不抛出已检查的异常,因此如果抛出任何异常,程序不会从中恢复,使资源恢复大多没有实际意义.

主要是因为它可能发生在一个线程中,线程死亡但程序仍在继续 - 比如说,暂时的内存中断不足以严重损害程序的其余部分.然而,这是一个相当不利的案例,如果它经常发生,足以使资源泄漏成为一个问题,那么资源尝试是你问题中最少的.

  • 这也是Effective Java第3版中推荐的方法. (2认同)

Jea*_*sky 5

选项4

如果可以,将资源更改为可关闭,而不是AutoClosable.构造函数可以链接的事实意味着关闭资源两次并不是闻所未闻.(在ARM之前也是如此.)更多内容如下.

选项5

不要非常小心地使用ARM和代码来确保close()不被调用两次!

选项6

不要使用ARM并在try/catch中自己进行finally close()调用.

为什么我认为这个问题不是ARM特有的

在所有这些示例中,finally close()调用应该在catch块中.遗漏以方便阅读.

不好,因为fw可以关闭两次.(这对于FileWriter来说很好,但在你的假设示例中没有):

FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( fw != null ) fw.close();
  if ( bw != null ) bw.close();
}
Run Code Online (Sandbox Code Playgroud)

没有好处,因为如果构造BufferedWriter时出现异常,则fw不会关闭.(再次,不可能发生,但在您的假设示例中):

FileWriter fw = null;
BufferedWriter bw = null;
try {
  fw = new FileWriter(file);
  bw = new BufferedWriter(fw);
  bw.write(text);
} finally {
  if ( bw != null ) bw.close();
}
Run Code Online (Sandbox Code Playgroud)