Wrapping Constructed Streams and Proper Closing

So I've just recently had one of those moments wherein I find out that something I assumed to be true was not. I had believed for quite some time that the closes in the following snippet were necessary (all of them):

FileOutputStream fos =new FileOutputStream(file);

GZIPOutputStream gzos =new GZIPOutputStream(fos);

ObjectOutputStream oos =new ObjectOutputStream(gzos);

os.writeObject(new Object());

oos.close();

gzos.close();

fos.close();

It wasn't until recently I found out that you can just do this:

OutputStream os =new ObjectOutputStream(new ObjectOutputStream(new FileOutputStream(file)));

os.writeObject(new Object());

os.close();

because the call to ObjectOutputStream.close() is expected to close all of the underlying streams.

Well, that's all delightful, but it doesn't address an error handling concern I have. In the chained constructor call, the FileOutputStream is constructed first, followed by the GZIPOutputStream and finally by the ObjectOutputStream. It is my understanding that I am to make sure that all of the streams I open are closed, whether directly or indirectly. What if ObjectOutputStream throws an exception? In that case, the FileOutputStream has been constructed but not closed and I do not have a reference to it.

My first impression is that I will have to keep a reference to it or (in this case) the GZIPOutputStream to ensure it is properly closed. Is this the only way to handle this problem? I know that finalize() will likely close it for me whenever the garbage collector finds it, but that's not guaranteed to happen at all, much less within a timely period.

So perhaps I will go on creating streams like I did before and not use so many close calls?

This is just me thinking aloud in a mildly tired state. But if anyone has any suggestions, I'd love to hear them.

Thanks. :)

[2202 byte] By [tvynra] at [2007-10-2 20:15:27]
# 1

Don't fret. Think of it this way. I have three output streams nested from #1 to #3. What should I do if #3 closes, but #2 throws an exception? Clearly, I cannot re-close #3. Attempting to close #2 is also a non-starter, as the previous attempt failed. What to do with #1? Well, it all depends. Nearly all streams flush() on a close(), and a good many perform additional closing operations (such as cryptographic streams) prior even to flush(), issuing additional calls to an underlying write().

So, IMO, once the process short-circuits, you should treat it as an unrecoverable exception. It may be recoverable, but you will probably invest significant intellectual and debugging effort to provide this.

- Saish

Saisha at 2007-7-13 22:57:45 > top of Java-index,Java Essentials,Java Programming...
# 2

But doesn't that mean that any system resources I might be using are still tied up? The FileOutputStream's resources would be freed as soon as it got garbage collected, but I can't assure that that is going to happen in any reasonable amount of time... so my JVM is consuming a spare open file handle or whatever else might've been tapped.

So, let's assume I'm paranoid or compulsive or whatever... can anyone think of a simpler or cleaner way of doing this?

OutputStream bos = null;

OutputStream os = null;

try

{

bos = new FileOutputStream(file); // base stream

os = new ObjectOutputStream(new GZIPOutputStream(bos));

// do stream stuff

} catch (IOException ioe)

{

// do exception handling stuff -- maybe a couple try-catch blocks go

// in the above try block to determine exactly which step threw the

// exception

} finally

{

try

{

if (os!=null) os.close();

} catch (IOException ioe)

{

// Fine... but let's try to free up any system resources which we used

if (bos!=null) bos.close();

throw ioe;

}

}

The only reason I concern myself with this is that I could easily see a program attempting the same operation a couple hundred times during its execution... and if it isn't really cycling any memory, the garbage collector might take its sweet time to clean things up. This means that I might be holding quite a lot of system resources by not addressing the problem.

tvynra at 2007-7-13 22:57:45 > top of Java-index,Java Essentials,Java Programming...
# 3

if os.close throws an exception (which is unlikely and may indicate a more serious problem that you can't deal with), it's just not worth the extra complexity of the code to go and explicitly close bos. You should rather assume that os.close will invoke bos.close and not guess as to whether or not it did or not before it threw the exception.

warnerjaa at 2007-7-13 22:57:45 > top of Java-index,Java Essentials,Java Programming...
# 4

None of these filters is actually likely to throw a checked exception in it's constructor anyway. The FileOutputStream is the only one really likely to throw, and at that stage none of them are open.

Nor do any of them except the FileOutputStream actually tie up real resources.

To be thorough you should probably close the outermost stream in a finally clause, but that's really all you need.

malcolmmca at 2007-7-13 22:57:45 > top of Java-index,Java Essentials,Java Programming...