在资源试用块中管理多个链接资源的正确习语?

2022-08-31 06:43:50

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

我想出了以下三个替代方案:

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

这很好,很短,但它坏了。由于基础不是在变量中声明的,因此它永远不会直接在生成的块中关闭。它只能通过包装的方法关闭。问题是,如果从 的构造函数引发异常,则不会调用它,因此基础不会被关闭FileWriterfinallycloseBufferedWriterbwcloseFileWriter

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

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

对于这两个都实现的特定类(这是 的子类型),这应该不是问题,它们的协定声明允许多次调用:CloseableAutoCloseableclose

关闭此流并释放与其关联的任何系统资源。如果流已关闭,则调用此方法不起作用。

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

请注意,与 java.io.Closeable 的 close 方法不同,此 close 方法不需要是幂等的。换句话说,多次调用此 close 方法可能会产生一些明显的副作用,这与 Closeable.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
    }
}

此版本在理论上应该是正确的,因为只有 表示需要清理的真实资源。本身不持有任何资源,它只委托给 ,因此仅关闭底层应该足够了。fwbwfwfw

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

资源泄漏:“bw”从未关闭


那么,选择哪种方法呢?还是我错过了其他一些正确的成语?


答案 1

以下是我对替代方案的看法:

1)

try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
    bw.write(text);
}

对我来说,15年前从传统C++来到Java的最好的事情是你可以信任你的程序。即使事情在泥泞中并且出错,他们经常这样做,我也希望代码的其余部分是最好的行为和玫瑰的味道。实际上,可能会在这里引发异常。例如,内存不足并不罕见。对于其他装饰器,您是否知道哪些包装类从其构造函数中抛出已检查的异常?我没有。如果你依赖这种晦涩难懂的知识,代码的可理解性并没有多大好处。BufferedWriterjava.io

还有“破坏”。如果存在错误情况,则您可能不希望将垃圾刷新到需要删除的文件(未显示其代码)。当然,尽管删除文件也是另一个有趣的操作,可以作为错误处理。

通常,您希望块尽可能短且可靠。添加刷新无助于此目标。对于许多版本,JDK 中的一些缓冲类存在一个错误,即未调用在修饰对象上导致的内部异常。虽然这个问题已经修复了一段时间,但期望它来自其他实现。finallyflushcloseclose

2)

try (
    FileWriter fw = new FileWriter(file);
    BufferedWriter bw = new BufferedWriter(fw)
) {
    bw.write(text);
}

我们仍然在隐式最终块中冲洗(现在重复 - 随着你添加更多装饰器,情况会变得更糟),但是构造是安全的,我们必须隐式最终块,所以即使失败也不会阻止资源释放。closeflush

3)

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
}

这里有一个错误。应该是:

try (FileWriter fw = new FileWriter(file)) {
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write(text);
    bw.flush();
}

一些实施不善的装饰器实际上是资源,需要可靠地关闭。此外,某些流可能需要以特定方式关闭(也许它们正在执行压缩并且需要写入位才能完成,并且不能只是刷新所有内容。

判决

虽然 3 是一个技术上更优越的解决方案,但软件开发原因使 2 是更好的选择。但是,尝试使用资源仍然是一个不充分的修复,您应该坚持使用“围绕执行”(Execute Around)的成语,该习语在Java SE 8中应该具有更清晰的闭包语法。


答案 2

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

主要是因为它可能发生在线程中,线程死亡但程序仍在继续 - 例如,有一个临时的内存中断,不足以严重损害程序的其余部分。但是,这是一个相当极端的情况,如果它经常发生,足以使资源泄漏成为问题,那么尝试使用资源是你遇到的问题最少的。


推荐