英文:
Is it good practice to try-with-resource a file writer
问题
我在网上和书籍《Effective Java》(由Joshua Bloch编写)中看到了这个示例。
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
writer.write(str); // 对我们打开的文件进行操作
} catch (IOException e) {
// 处理异常
}
这个示例没有问题,BufferedWriter
会自动关闭,从而也会关闭FileWriter
;然而,在其他情况下,如果我们像这样声明了2个嵌套的资源:
try (AutoClosable res = new Impl2(new Impl1())) {... }
我猜可能出现new Impl1()
表现良好,但new Impl2()
崩溃的情况,在这种情况下,Java将无法引用Impl1
,以便关闭它。
难道在实践中总是独立地声明多个资源(即使在此情况下不需要)会更好,像这样:
try (FileWriter fw = new FileWriter(fileName);
BufferedWriter writer = new BufferedWriter(fw)) { ... }
英文:
I saw this example on the web and in the Book "Effective Java" (by Joshua Bloch).
try(BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))){
writer.write(str); // do something with the file we've opened
}
catch(IOException e){
// handle the exception
}
There is no problem with this example, BufferedWriter
which will automatically get closed and that, in turn, will close the FileWriter
; however, in other case, if we declare 2 nested resources this way:
try (AutoClosable res = new Impl2(new Impl1())) {... }
I guess it might happen that new Impl1()
performs fine, but new Impl2()
crashes, and in this case Java would have no reference to the Impl1
, in order to close it.
Shouldn't it be a better practice to always declare multiple resources independently (even if not required in this case), like this?
try(FileWriter fw = new FileWriter(fileName);
BufferedWriter writer = new BufferedWriter(fw)){ ... }
答案1
得分: 16
经过一些搜索,我找到了这篇文章:https://dzone.com/articles/carefully-specify-multiple-resources-in-single-try
根据JLS 14.20.3的定义,ResourceList
由 Resource
以 ;
分隔组成。基于这个定义,我们可以得出结论,像 AutoClosable res = new Impl2(new Impl1())
这样的嵌套初始化是一个单一资源。因此,不适用于具有多个资源的 try-with-resources
的规则,其中一些重要规则包括:
-
资源按从左到右的顺序初始化。如果某个资源初始化失败(也就是说,它的初始化表达式引发了异常),那么由
try-with-resources
语句初始化的到目前为止的所有资源都将关闭。如果所有资源都成功初始化,那么try
块将正常执行,然后关闭try-with-resources
语句中的所有非空资源。 -
资源按初始化的相反顺序关闭。只有初始化为非空值的资源才会关闭。一个资源的关闭异常不会阻止其他资源的关闭。如果之前由初始化器、
try
块或资源关闭引发了异常,那么这种异常会被抑制。
此外,除非在 Impl2#close()
内显式调用,否则 Implt1#close()
将不会被调用。
简而言之,最好在单独的语句中声明多个资源,并用 ;
分隔,就像这样:
try(Impl1 impl1 = new Impl1();
Impl2 impl2 = new Impl2(impl1))
英文:
After some searching I was able to find this article: https://dzone.com/articles/carefully-specify-multiple-resources-in-single-try
By definition JLS 14.20.3 a ResourceList
consists of Resource
s separated by ;
. Based on that we can conclude that a nested initialization like AutoClosable res = new Impl2(new Impl1())
is a single resource. Because of that rules defined for try-with-resources
with multiple resources won't apply here, important ones being:
> Resources are initialized in left-to-right order. If a resource fails to initialize (that is, its initializer expression throws an exception), then all resources initialized so far by the try-with-resources statement are closed. If all resources initialize successfully, the try block executes as normal and then all non-null resources of the try-with-resources statement are closed.
> Resources are closed in the reverse order from that in which they were initialized. A resource is closed only if it initialized to a non-null value. An exception from the closing of one resource does not prevent the closing of other resources. Such an exception is suppressed if an exception was thrown previously by an initializer, the try block, or the closing of a resource.
What is more, Implt1#close()
won't be called unless it is explicitly called inside of Impl2#close()
In short, it is better to declare multiple resources in separate statements separated with ;
, like so:
try(Impl1 impl1 = new Impl1();
Impl2 impl2 = new Impl2(impl1))
答案2
得分: 3
如果在 try 部分发生异常,则不会发生资源泄漏(假设 Impl1 编写良好)。请注意,Impl1()
中引发的异常不会传达到 Impl2
,因为构造函数参数在调用它之前被求值。
try (AutoClosable res = new Impl2(new Impl1())) {
因此,嵌套此类包装构造函数是可以的;如果代码不会变得过长,这是更好的风格。
一个注意事项:FileWriter
和 FileReader
是使用平台编码的旧实用程序类,这会根据应用程序安装而异。
Path path = Paths.get(fileName);
try (BufferedWriter writer =
Files.newBufferedWriter(path, StandardCharsets.UTF8)) {
英文:
If the exception happens in the try part, there is no resource leakage (assuming Impl1 is written well). Notice that a raised exception in Impl1()
will not reach Impl2
as the constructor argument is evaluated before calling it.
try (AutoClosable res = new Impl2(new Impl1())) {
So it is fine to nest such wrapping constructors; better style if the code does not become to long.
One remark: FileWriter
and FileReader
are old utility classes using the platform encoding, which will differ per application installation.
Path path = Paths.get(fileName);
try (BufferedWriter writer =
Files.newBufferedWriter(path, StandardCharsets.UTF8)) {
答案3
得分: 2
首先,为了进行一次小的健全性检查,我想说,我在 Joshua Bloch 的《Effective Java》(第三版,2018年)中未能找到你提供的示例。如果你正在阅读之前的版本,最好获取最新版本。如果是我的错误,请向我提供具体的页码。
现在针对问题本身。
让我们从JSL §14.20.3开始,它指出资源由 ;
*分隔。*这意味着,无论我们的对象创建装饰链有多长(例如new Obj1(new Obj2(...new ObjK(..)));
),它都将被视为一个单一资源,因为它是一个定义/语句。
既然我们现在知道了单一资源的构成,让我从我的观察中解释一些事情。
定义资源分开的优势
- JSL §14.20.3 也指出:
> 如果资源无法初始化(即其初始化表达式引发异常),则到目前为止由try-with-resources语句初始化的所有资源都将关闭。如果所有资源都成功初始化,则try块将正常执行,然后try-with-resources语句的所有非空资源都将关闭。
Q:这对我们意味着什么?
A:如果单一资源是传递到包装构造函数中的对象链,这并不意味着从初始化阶段引发异常将关闭这些嵌套资源,而是将在父(封装)对象上调用.close()
方法,因此,你可能会出现资源泄漏的情况。
另一方面,如果你将它们分开定义,你可以确信所有资源将被关闭。
- JSL §14.20.3 还指出:
> 一个资源的关闭异常不会阻止其他资源的关闭。如果之前由初始化程序、try块或关闭资源引发了异常,则将抑制此类异常。
Q:这对我们意味着什么?
A:如果你已经单独声明了资源,那么引发异常与否并不重要。它们都将成功关闭。
- 不幸的是,你提到的书(至少是第三版)没有涵盖你在这里提出的问题。然而,我进行了更多的研究,发现 Cay S. Horstmann 的《Core Java》(第10版)在其§7.2.5中确认了我上面提到的观点:
> 当块正常退出或存在异常时,将调用in.close()
方法,就像你使用了finally块一样。
以及
> 无论块如何退出,都会关闭in
和 out
。
<sub>在书中给出的示例中,in
和 out
都是可自动关闭的对象。</sub>
Q:这是什么意思?
A:这意味着从任何资源的任何阶段抛出的异常都不会以任何方式影响其他资源的关闭。
基于上述所有内容,我认为这也取决于资源的实现方式。例如,如果在调用其.close()
时,封闭资源实现了关闭嵌套资源的逻辑,那么你提到的担忧:
> 我猜可能会出现 new Impl1() 表现良好,但 new Impl2() 崩溃的情况,在这种情况下,Java 将无法引用 Impl1 以关闭它。
在你的特定情况下实际上不会成为问题,因为被关闭的容器资源将持有对包含的资源的引用,并最终关闭它(或者只是实现其关闭);
然而,由于我提出了几个原因,将资源构建在包装构造函数的装饰中以链接它们仍然是一个不好的主意。
附言
无论如何,不管资源是如何实现或如何链接它们等等,所有内容,包括JLS、Core Java 书籍、OCP Java SE 8 书籍以及这里提到的要点,都表明最好始终将资源单独声明。
英文:
First of all, for the sake of a little sanity check, I would say, that I could not find the example, you provided, in Joshua Bloch's Effective Java (3rd Edition, 2018). If you are reading the previous version, it should be better to get the latest one. If it is my bad, please refer particular page number to me.
Now with respect to the question itself.
Let's begin with JSL §14.20.3, which says, that Resources are separated by ;
. This means, that disregarding of how long our decoration chain of the object creations will be (e.g. new Obj1(new Obj2(...new ObjK(..)));
it will be treated as a one single resource, as it is a one definition/statement.
As we now know what does a Single Resource constitute, let me shed some light from my observation.
Reasons, why it is better to define resources separately
- JSL §14.20.3 also states, that:
> If a resource fails to initialize (that is, its initializer expression throws an exception), then all resources initialized so far by the try-with-resources statement are closed. If all resources initialize successfully, the try block executes as normal and then all non-null resources of the try-with-resources statement are closed.
Q: What does that mean for us?
A: If that single resource is a chain of objects passed into wrapping constructors, it does not mean, that throwing an exception from the initialization phase will close those embedded resources, rather the .close()
method will be invoked on the parent (wrapper, enclosing) objects, and therefore, you might easily end up with a resource leak.
On the other hand, you can be resting assured that all resources will be closed, if you have defined them separately.
- JSL §14.20.3 also states, that:
> An exception from the closing of one resource does not prevent the closing of other resources. Such an exception is suppressed if an exception was thrown previously by an initializer, the try block, or the closing of a resource.
Q: What does that mean for us?
A: If you have declared your resources separately, then it does not matter which throws exception and which does not. They all will be closed successfully.
- Unfortunately the book you mentioned (at least the 3rd version it) does not cover the question you brought up here; however, I did some more research and found that Core Java (10th Edition), by Cay S. Horstmann, confirms the point I referred to above, in its §7.2.5:
> When the block exits normally, or when there was an exception, the in.close()
method is called, exactly as if you had used a finally block.
and
> No matter how the block exits, both in
and out
are closed.
<sub>in
and out
are Autoclosable objects in the examples given in the book.</sub>
Q: What does this mean?
A: It means, that exceptions thrown from any phase of one of the resources, does not anyhow affect how another resources are closed.
Based on all above, I think, that it also depends on how the resources are implemented. E.g. if the enclosing resource, upon an invocation of its .close()
, implements the logic to close the enclosed resource, then your concern:
> I guess it might happen that new Impl1() performs fine, but new Impl2() crashes, and in this case Java would have no reference to the Impl1, in order to close it.
will not really be a problem in your particular case, as the container resource which is being closed, will hold a reference to the contained resource and eventually will close it (or will just implement its closure);
However, it is still a bad idea to construct resources in the decoration of chaining them into wrapper constructors, due to several reasons I brought up.
P. S.
In any case, disregarding of how the resources are implemented or how you chain them or etc. everything, including JLS, Core Java book, OCP Java SE 8 book, and points brought here, indicate that it's always best to declare your resources separately.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论