Part of Project Coin is the ability to deal with Automatic Resource Management, or simply ARM. The purpose is to make it easier to work with external resources which need to be disposed or closed in case of errors or successful completion of a code block. Consider the following trivial file-copy operation, from the Java Bytestream Tutorial:
FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("xanadu.txt"); out = new FileOutputStream("outagain.txt"); int c; while ((c = in.read()) != -1) out.write(c); } finally { if (in != null) in.close(); if (out != null) out.close(); }
Not only is there a lot of boiler plate, but the documentation for InputStream.close()
suggests that it can throw an IOException
. (An exception is far more likely on the OutputStream
but in any case, there needs to be an outer catch
or propagation in order to successfully compile this code.)
The lexical scope of the try-catch-finally
block also requires the variable for FileInputStream in
and FileOutputStream out
to be declared lexically outside the block itself. (If they were defined inside the try
block, then they wouldn't be available inside the catch
or finally
blocks.)
To eliminate this boilerplate code, and to tighten the lexical scoping of the resources used inside the block, a new addition has been made to the try
block in the Java language. An initial specification of the try-with-resources blocks (or ARM blocks) was made available via an ininitial implementation, which has subsequently made its way into build 105 of JDK 7.
A new interface, java.lang.AutoCloseable
, has been added to the proposed API, which defines a single method close()
which throws Exception
. This has been retro-fitted as a parent of java.io.Closeable
, which means that all InputStream
and OutputStream
automatically take advantage of this behaviour. In addition, FileLock
and ImageInputStream
have also been fitted with the AutoCloseable
interface.
This permits the above example to be written as:
try ( FileInputStream in = new FileInputStream("xanadu.txt"); FileOutputStream out = new FileOutputStream("outagain.txt") ) { int c; while((c=in.read()) != -1 ) out.write(); }
At the end of the try
block, whether by completion normally or otherwise, both the out
and in
resources will have close()
called automatically. Furthermore, unlike our original example, both out.close()
and in.close()
are guaranteed to be executed. (In the original example, had in.close()
thrown an exception, then the subsequent out.close()
would not have been executed.)
There are some subtle aspects to this which are worth noting:
- As it currently stands, a trailing semi-colon is not permitted after the final resource in the resources section.
- The resources block is separated with
()
rather than the more usual{}
, to separate it from the existing try body. If present, it must contain one or more resource definitions. - Each resource definition is of the form type var = expression; you can't have general statements in the resource block.
- The resources are implicitly final; that is, they behave as if the
final
modifier is present. Any attempt to assign to the resource variable is a compile-time error. - The resources must be a subtype of
AutoCloseable
; it is a compile-time error if this is not the case. - The order of closure is the reverse order in which the resources are defined. In other words, in the refined example,
out.close()
is called prior toin.close()
. This permits nested streams to be built and then closed from outer-in, which makes more sense than in order (e.g. for flushing buffers before the underlying stream is closed). - Each block may generate n+1 exceptions, where n is the number of resources. This can occur if the main body throws an exception, and each resource closure throws an exception as well. In this situation, the body's exception will be thrown, but the others will be added to the exception's suppressed exception list. They can be accessed via
getSuppressedExceptions()
. - Exception stack traces may now have code prefixed with
Suppressed:
in these cases; the format of the serializedThrowable
is now different as well. (This may impact Java 6 clients invoking remote services on Java 7 runtimes, or vice-versa.) javax.swing
andjava.sql
do not participate in ARM at the current time; classes need to opt-in by inheriting fromAutoCloseable
to be used by ARM. JDBC 4.1, if part of JDK 7, will support ARM but it's not clear when this will happen.
The ability to remove boilerplate code from the Java developer's work-flow is likely to be a minor productivity boost; but although it's available in JDK 7, it will be sometime before source code can be written to take advantage of that fact. Many libraries will need to be compiled to run against Java 6; and any use of the automatic resource management will only be applicable for code compiled with -target 7
(or similar). Once Java 6 is EOL and Java 8 has been released, then using ARM will become an automatic way of working.
Community comments
Hmmm.....
by Clint Farleigh,
About freaking time!!!!!!!
by Matt Giacomini,
Learning from others
by Patrick Dreyer,
Re: Learning from others
by James Watson,
Re: Learning from others
by David Birdsall,
Re: Learning from others
by Rob Elliot,
Hmmm.....
by Clint Farleigh,
Your message is awaiting moderation. Thank you for participating in the discussion.
I think I've seen this somewhere before... maybe about 5 years ago? :-)
About freaking time!!!!!!!
by Matt Giacomini,
Your message is awaiting moderation. Thank you for participating in the discussion.
.
Learning from others
by Patrick Dreyer,
Your message is awaiting moderation. Thank you for participating in the discussion.
One of the first lessons we as parents teach to our children is: Learn from others.
Why does this not apply to programming languages?
Try-catch-finally is about error handling and not about ARM.
Thus, why complicate the try-catch-finally instead introducing "using" as .NET is using it?
Have a look at msdn.microsoft.com/en-us/library/yh598w02%28VS.... it's simple, clear and straight forward.
This way we can even rename "java.lang.AutoCloseable" to "java.lang.Closeable" as it's not about auto-closing but about a clearly programmed/stated behavior being part of "using".
It's all about taking advantage of the fillets of each language.
Note: I'm not going into a debate .NET vs. Java - I won't.
Re: Learning from others
by James Watson,
Your message is awaiting moderation. Thank you for participating in the discussion.
I have to agree that using the try keyword for this doesn't seem very good. I have to guess that the reason a new keyword wasn't used (such as 'using') is because the fear that existing code will not compile. Although, this did not prevent enum from being added which was more likely (I guess) to be used as a name in existing code.
Re: Learning from others
by David Birdsall,
Your message is awaiting moderation. Thank you for participating in the discussion.
what if it could be implemented using closures? Would coin's new syntax be able to do something similar to this, but without the extra boilerplate:
...where Closeable.close is a static generic method that returned an object specifically used for closing after reading. Could even be a static import, but you'd still have to define the read(T) override.
At least it would be re-using another feature of the language (closures) and not introducing another keyword.
When I see the using() {} statement in C# it makes me think of blocks in languages like Ruby/Groovy.
Re: Learning from others
by Rob Elliot,
Your message is awaiting moderation. Thank you for participating in the discussion.
I'd have thought it would just be a method on Closeable. So you could do something like:
Potentially if you made the instance variable final you wouldn't even need to pass it in as an argument to the lambda. The "with" method would call the closure in a try block and close itself cleanly in the finally block.
It does seem odd that this should be implemented at the same time as lambdas if lambdas would allow it to be implemented cleanly without further new syntax. Perhaps there's some subtlety to it that I'm missing.