Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does nested resource acquisition require special handling in Java?

First of all, yes, try-with-resource fixes any of these questions... but I can't see how this exactly works without it.

Let's look at this code from the java documentation as an example, which can be found here:

static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

Now, the resource is released on br.close() if it was acquired. However,

  • What happens if new FileReader(path) succeeds and then new BufferedReader(...) throws an exception?
  • How does java guarantee that the FileReader is closed?
  • Is it guaranteed that creating a BufferedReader on an already opened FileReader will always succeed? If so, why is that method declared as throwing an IOException?

Or should we instead be writing the following to make sure that situation doesn't happen?

static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {

    FileReader fr;

    try {
        FileReader fr = new FileReader(path);
        BufferedReader br;
        try {
            BufferedReader br = new BufferedReader(fr);
            return br.readLine();
        } finally {
            if (br != null) br.close();
        }
    finally {
        // Implements closeable, so it is ok if we call it twice.
        if (fr != null) fr.close();
    }
}


Of course, using try-with-resources, this nested mess disappears nonetheless, since we can declare multiple resources in the same statement. But I always see myself writing try-with-resources as a way to avoid thinking at all about exactly this situation, and trying to find a solution online I really couldn't.

Any help would be appreciated, thanks!

like image 848
Alex Recuenco Avatar asked Jan 25 '19 02:01

Alex Recuenco


1 Answers

The tutorials are often out of date or don't describe good practice.

If we look at the first piece of code you quoted, br can never be null. The if ( ) can be removed. This was probably caused by previous code by previous code mixing to finally with a catch. If a try statement has both finally and a catch it is probably wrong and almost certainly doing something ill advised. Typically you'd see a null dance going on and at least one obvious bug.

The received wisdom on this issue is that BufferedReader will only fail if something has gone horribly wrong with your entire process. Perhaps out of memory or the stack have overflowed. If you get those sorts of exceptions you probably want to bail out of altogether.

The pedantic way of writing the code without try-with-resource is:

FileReader fr = new FileReader(path);
try {
    BufferedReader br = new BufferedReader(fr);
    return br.readLine();
} finally {
    fr.close();
}

However, in some case you may get this wrong. Considered BufferWriter. You've forgotten to flush that, haven't you? I mean, I would. If you closed that in a finally, it wouldn't have been a problem. Also some decorators are resources themselves. For instance, they may have a native implementation that uses non-garbage collected memory. That isn't necessarily documented.

Closing both resource and decorator isn't difficult, but does kind of head towards the right without try with resource.

// (Using same example even though it doesn't matter here - imaging a Writer)
FileReader fr = new FileReader(path);
try {
    BufferedReader br = new BufferedReader(fr);
    try {
        return br.readLine();
    } finally {
        br.close();
    }
} finally {
    fr.close();
}
like image 147
Tom Hawtin - tackline Avatar answered Oct 22 '22 19:10

Tom Hawtin - tackline