【发布时间】:2012-09-15 04:40:09
【问题描述】:
Java 7 try-with-resources 语法(也称为 ARM 块 (Automatic Resource Management))在仅使用一个 @987654321 时非常简洁明了@资源。但是,当我需要声明多个相互依赖的资源时,我不确定什么是正确的习惯用法,例如 FileWriter 和包装它的 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
}
}
这很好,很短,但它坏了。因为底层的FileWriter 没有在变量中声明,所以它永远不会在生成的finally 块中直接关闭。它将仅通过包装BufferedWriter 的close 方法关闭。问题是,如果从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
}
}
这里,底层资源和包装资源都声明在 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
}
}
这个版本理论上应该是正确的,因为只有fw代表了需要清理的真实资源。 bw 本身不持有任何资源,它只委托给fw,因此只关闭底层fw 就足够了。
另一方面,语法有点不规则,而且 Eclipse 发出警告,我认为这是一个误报,但它仍然是一个必须处理的警告:
资源泄漏:'bw' 永远不会关闭
那么,采用哪种方法呢?还是我错过了其他一些正确的成语?
【问题讨论】:
-
当然,如果底层 FileWriter 的构造函数抛出异常,它甚至不会被打开,一切正常。第一个示例的内容是,如果 FileWriter 被创建,但 BufferedWriter 的构造函数抛出异常,会发生什么。
-
值得注意的是BufferedWriter不会抛出异常。有没有一个你能想到的例子,这个问题不是纯粹的学术问题。
-
@PeterLawrey 是的,你说得对,在这种情况下 BufferedWriter 的构造函数很可能不会抛出异常,但正如我所指出的,这个问题涉及任何装饰器风格的资源。但例如
public BufferedWriter(Writer out, int sz)可以抛出IllegalArgumentException。此外,我可以使用一个类扩展 BufferedWriter,该类会从其构造函数中抛出一些东西,或者创建我需要的任何自定义包装器。 -
BufferedWriter构造函数可以轻松抛出异常。OutOfMemoryError可能是最常见的一个,因为它为缓冲区分配了相当大的内存块(尽管可能表明您要重新启动整个进程)。 /如果您不关闭并希望保留内容(通常仅非异常情况),则需要flush您的BufferedWriter。FileWriter选择“默认”文件编码 - 最好是明确的。 -
@Natix 我希望 SO 中的所有问题都像这个问题一样得到充分研究和明确。我希望我能投票 100 次。