Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct idiom for managing multiple chained resources in try-with-resources block?

People also ask

What is a try-with-resources?

The try -with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try -with-resources statement ensures that each resource is closed at the end of the statement.

Which of the following is correct statement about try-with-resources statement in Java 9?

The try-with-resources statement is a try statement with one or more resources duly declared. Here resource is an object which should be closed once it is no more required. The try-with-resources statement ensures that each resource is closed after the requirement finishes.

How do you handle exceptions in trying with resources?

If an exception is thrown from within a Java try-with-resources block, any resource opened inside the parentheses of the try block will still get closed automatically. The throwing of the exception will force the execution to leave the try block, and this will force the automatic closing of the resource.

How do you signal to Java that an object can be used in a try-with-resources block?

The try-with-resources statement ensures that each resource is closed at the end of the statement execution. If we don't close the resources, it may constitute a resource leak and also the program could exhaust the resources available to it. You can pass any object as a resource that implements java.


Here's my take on the alternatives:

1)

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

For me, the best thing coming to Java from traditional C++ 15 years ago was that you could trust your program. Even if things are in the muck and going wrong, which they often do, I want the rest of the code to be on best behaviour and smelling of roses. Indeed, the BufferedWriter might throw an exception here. Running out of memory wouldn't be unusual, for instance. For other decorators, do you know which of the java.io wrapper classes throw a checked exception from their constructors? I don't. Doesn't do code understandability much good if you rely upon that sort of obscure knowledge.

Also there's the "destruction". If there is an error condition, then you probably don't want to be flushing rubbish to a file that needs deleting (code for that not shown). Although, of course, deleting the file is also another interesting operation to do as error handling.

Generally you want finally blocks to be as short and reliable as possible. Adding flushes does not help this goal. For many releases some of the buffering classes in the JDK had a bug where an exception from flush within close caused close on the decorated object not be called. Whilst that has been fixed for some time, expect it from other implementations.

2)

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

We're still flushing in the implicit finally block (now with repeated close - this gets worse as you add more decorators), but the construction is safe and we have to implicit finally blocks so even a failed flush doesn't prevent resource release.

3)

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

There's a bug here. Should be:

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

Some poorly implemented decorators are in fact resource and will need to be closed reliably. Also some streams may need to be closed in a particular way (perhaps they are doing compression and need to write bits to finish off, and can't just flush everything.

Verdict

Although 3 is a technically superior solution, software development reasons make 2 the better choice. However, try-with-resource is still an inadequate fix and you should stick with the Execute Around idiom, which should have a clearer syntax with closures in Java SE 8.


The first style is the one suggested by Oracle. BufferedWriter doesn't throw checked exceptions, so if any exception is thrown, the program is not expected to recover from it, making resource recover mostly moot.

Mostly because it could happen in a thread, with the thread dieing but the program still continuing -- say, there was a temporary memory outage that wasn't long enough to seriously impair the rest of the program. It's a rather corner case, though, and if it happens often enough to make resource leak a problem, the try-with-resources is the least of your problems.


Option 4

Change your resources to be Closeable, not AutoClosable if you can. The fact that the constructors can be chained implies it isn't unheard of to close the resource twice. (This was true before ARM too.) More on this below.

Option 5

Don't use ARM and code very carefully to ensure close() isn't called twice!

Option 6

Don't use ARM and have your finally close() calls in a try/catch themselves.

Why I don't think this problem is unique to ARM

In all these examples, the finally close() calls should be in a catch block. Left out for readability.

No good because fw can be closed twice. (which is fine for FileWriter but not in your hypothetial example):

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();
}

No good because fw not closed if exception on constructing a BufferedWriter. (again, can't happen, but in your hypothetical example):

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

I just wanted to build on Jeanne Boyarsky's suggestion of not using ARM but making sure the FileWriter is always closed exactly once. Don't think there are any problems here...

FileWriter fw = null;
BufferedWriter bw = null;
try {
    fw = new FileWriter(file);
    bw = new BufferedWriter(fw);
    bw.write(text);
} finally {
    if (bw != null) bw.close();
    else if (fw != null) fw.close();
}

I guess since ARM is just syntactic sugar, we can't always use it to replace finally blocks. Just like we can't always use a for-each loop to do something that is possible with iterators.