Automatic Resource Management Blocks
Joshua Bloch
I. The Problem
With garbage collection, the Java programming language greatly reduces the need for manual resource management. However, it is still necessary to dispose of certain resources manually. Examples include
java.io.InputStream,
OutputStream,
Reader,
Writer,
Formatter;
java.nio.Channel;
java.sql.Connection,
Statement, and
ResultSet.
The code to dispose of resources manually is ugly and error prone:
BuffereInputStream bis = null; try { BuffereInputStream bis = ...; ... ; // Perform action with bis1 } finally { if (bis != null) bis.close(); }The code is even uglier if multiple resources are used together:
BuffereInputStream bis1 = null; BuffereInputStream bis2 = null; try { BuffereInputStream bis1 = ...; BuffereInputStream bis2 = ...; ... ; // Perform action with bis1 and bis2 } finally { try { if (bis1 != null) bis1.close(); } finally { if (bis2 != null) bis2.close(); } }Note that the nesting depth is proportional to the number of resources used. Failure to nest the try-finally statements results in a subtle resource leak (
Java Puzzlers , Puzzle 41):
BuffereInputStream bis1 = null; BuffereInputStream bis2 = null; try { bis1 = ...; bis2 = ...; ... ; // Perform action with bis1 and bis2 } finally { // LEAK: bis2 WON'T GET CLOSED IF ATTEMPT TO CLOSE bis1 FAILS!!! if (bis1 != null) bis1.close(); if (bis2 != null) bis2.close(); }The price for failing to dispose of resources properly is resource leaks, leading to performance problems and even outright failures. Other languages, such as C++ and C#, have language constructs to automatically release resources when control transfers out of a block. For C++, it is the destructor; for C#, it is the
using statement. Java deserves no less. In fact, it deserves better: The only way to handle multiple resources with
using statements is to nest them, which is ugly.
Also the using-statement does handle resources that require initialization as well as termination. For example, consider a non-built-in lock (
java.util.concurrent.locks.Lock). The following boilerplate is required to execute code under the protection of such a lock:
Lock lck = ...; lck.lock(); try { ... // access the resource protected by this lock } finally { lck.unlock(); }II. The Solution
The solution is to provide some backward-compatible syntactic sugar to automatically invoke the disposal method (and initialization method, if applicable) automatically upon exit (and entry) to the scope. An interface is used to make a class eligible for automatic disposal. An obvious choice is java.io.Closeable, though its ties to java.io are unfortunate. A possible syntax is:
do (BufferedInputStream bis = ...) { ... // Perform action with bis }This has the added advantage of restricting the scope of the resource variable (bis) to the block in which it is valid, in much the same way that a for loop restricts the scope of the loop variable(s).
A possible syntax for a block that dispose of multiple resources:
do (BufferedInputStream bis1 = ..., bis2 = ...) { ... // Perform action with bis1 and bis2 }This requires all of the resources to be of the same type. Another possible syntax is:
do (BufferedInputStream bis = ...; BufferedOutputStream bos = ...) { ... // Perform action with bis and bos }In some cases there may be no need to access the resource variable inside the statement, so perhaps the declaration could be replaced by an expression of an appropriate type:
do (new Transaction()) { ... // Perform action transactionally }It may be worth providing an alternative syntax for java.ultil.concurrent locks:
protected (lock) { ... // access the resource protected by this lock }While this is pretty, the use of these locks is comparatively rare, so it is not clear whether this final bit of syntactic sugar pays for itself.
III. Open Questions
The description above leaves a number of important syntactic and semantic details unspecified. Here is a partial list.
(1) Is the "
do" syntax above best? Or would we be better of doing something with "
try", which would allow for catch blocks and finally, e.g.:
try (new Transaction()) { ... // Perform action transactionally } catch(AbortException e) { ... }(2) Should there be a separate "
protected" construct for
java.util.concurrency locks, as described above? If so, should it permit multiple locks?
(3) How do you deal with exceptions during disposal? If an exception was thrown by the block itself, does an exception during termination obliterate it? If an exception is thrown during initialization of a resource after successful initialization of a second resource, and a second exception is throw when disposing of the first resource, does it obliterate the first exception? And so on.
(4) What interface should we use for automatic disposal? Much as I'd like to use Closeable, many existing types (such as
java.sql.Connection,
Statement,
ResultSet, etc.) could not be made to implement it, as they throw other unchecked exceptions. Probably we'll have to make an interface something like this, and retrofit existing classes:
package java.lang; public interface Foo<X extends Exception> { void close() throws X; }Of course we can't call it Foo, and Closeable is already taken, so what do we call it? Will it be possible to parameterize the the interface in the exception type that it throws? If not, should we wrap any exceptions thrown by close, and should the wrapping exception be unchecked or checked?
(5) Should we support automatic initialization as well as disposal? If so, what interface should we call the interface? How will it look? Something like this, perhaps?
package java.lang; public interface OpenAndCloseable<X1 extends Exception, X2 extends Exception> { void open() throws X1; void close() throws X2; }(6) Should we do something to make it easy to ignore exceptions that occur during disposal in cases where the programmer judges it to be appropriate (e.g., closing a file after reading it)?
I'm sure there are plenty of other open questions, but these are the ones that come immediately to mind, and should give some flavor of the design space.