try-with-resources 在哪里用 InputStreamReader 包装流?

try-with-resources where to wrap stream with InputStreamReader?

我可能想多了,但我只是写了代码:

try (InputStream in = ModelCodeGenerator.class.getClassLoader().getResourceAsStream("/model.java.txt"))
{
    modelTemplate = new SimpleTemplate(CharStreams.toString(new InputStreamReader(in, "ascii")));
}

这意味着 InputStreamReader 永远不会关闭(但在这种情况下我们知道它的关闭方法只是关闭底层的 InputStream。)

可以写成:

try (InputStreamReader reader = new InputStreamReader(...))

但这似乎更糟。如果 InputStreamReader 由于某种原因抛出异常,InputStream 永远不会关闭,对吧?这是 C++ 中构造函数调用其他构造函数的常见问题。异常可能导致 memory/resource 泄漏。

这里有最佳实践吗?

But this seems worse. If InputStreamReader throws for some reason, the InputStream won't ever be closed, right?

没错(虽然不太可能,InputStreamReader 构造函数并没有真正做太多)。

try-with-resources 允许您声明任意数量的资源。为包装资源声明一个,为 InputStreamReader.

声明另一个
try (InputStream in = ModelCodeGenerator.class
             .getClassLoader()
             .getResourceAsStream("/model.java.txt");
    InputStreamReader reader = new InputStreamReader(in)) {...}

请注意 getResourceAsStream 可能 return null,这将导致 InputStreamReader 构造函数抛出 NullPointerException。如果您想以不同的方式处理,请调整您检索要包装的资源的方式。

上面链接的教程介绍了这个例子

try (
    java.util.zip.ZipFile zf =
         new java.util.zip.ZipFile(zipFileName);
    java.io.BufferedWriter writer = 
        java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
) {

有说明

In this example, the try-with-resources statement contains two declarations that are separated by a semicolon: ZipFile and BufferedWriter. When the block of code that directly follows it terminates, either normally or because of an exception, the close methods of the BufferedWriter and ZipFile objects are automatically called in this order. Note that the close methods of resources are called in the opposite order of their creation.

Which means the InputStreamReader is never closed

诶?在你的代码中它是......它肯定也会处理你的资源流的 .close() 。有关更多详细信息,请参见下文...

但是您可以在 try-with-resources 语句的 "resource block" 中声明多个资源。

你这里还有一个问题:.getResourceAsStream() can return null;因此,您可能有 NPE。

如果我是你,我会这样做:

final URL url = ModelCodeGenerator.class.getClassLoader()
    .getResource("/model.java.txt");

if (url == null)
    throw new IOException("resource not found");

try (
    final InputStream in = url.openStream();
    final Reader reader = new InputStreamReader(in, someCharsetOrDecoder);
) {
    // manipulate resources
}

有一个非常重要的问题需要考虑但是...

Closeable 确实扩展了 AutoCloseable,是的;事实上,它只是 "signature wise" 抛出的异常不同(IOExceptionException)。但是行为.

有根本的区别

来自 AutoCloseable.close() 的 javadoc(强调我的):

Note that unlike the close method of Closeable, this close method is not required to be idempotent. In other words, calling this close method more than once may have some visible side effect, unlike Closeable.close which is required to have no effect if called more than once. However, implementers of this interface are strongly encouraged to make their close methods idempotent.

确实,Closeable 的 javadoc 对此很清楚:

Closes this stream and releases any system resources associated with it. If the stream is already closed then invoking this method has no effect.

你有两个非常重要的观点:

  • 根据合同,Closeable 还负责处理与其关联的所有资源;所以,如果你关闭一个 BufferedReader,它包裹着一个 Reader,它包裹着一个 InputStream,所有三个都关闭;
  • 如果您多次调用 .close(),则没有进一步的副作用。

当然,这也意味着您可以选择偏执选项并保留对所有 Closeable资源的引用并将它们全部关闭;但是请注意,如果您有 AutoCloseable 资源,但不是 Closeable!