Automatic Resource Management, V.2 (wrt. 'finally')

Derek Foster vapor1 at teleport.com
Mon Apr 20 17:21:00 PDT 2009



-----Original Message-----
>From: Joshua Bloch <jjb at google.com>
>Sent: Apr 20, 2009 9:19 AM
>To: coin-dev <coin-dev at openjdk.java.net>
>Subject: Re: Automatic Resource Management, V.2
>
>Neal,
>
>I do believe I addressed all the substantive issues that you raised:
>
>>(1) "try" is not a natural keyword for the intended semantics.
>
>> (3) The nesting of new behavior with old behavior in the try statement
>> is a poor fit for many clients. [Discussion of LineNumberReader snipped]
>
>You didn't really back this one up.  Other people have commented on what a
>great fit they think it is.  So we'll just have to consider it an issue of
>taste, on which reasonable men may disagree.

Hi, Josh.

As much as I agree with the general goals of the ARM proposal, I'd like to point out that Neal isn't the only one to hold this opinion. I had the same reaction as Neal did when I saw that "try" was being used for this purpose. The 'try..catch..finally' construct is already one of the more complicated constructs in Java. I hate to see it complicated further by adding new functionality to it that really has nothing to do with its primary purpose (that of attempting an operation and then providing places for someone to handle exceptions from it). Also, I hate to see a mechanism that is basically type-agnostic be expanded with something that is so specific to file-based I/O.

Your recent ARM proposal description, for instance, was complicated by the question of what to do with any other 'catch' or 'finally' clauses which might be present on the 'try'. The proposal was also somewhat unclear on whether or not a 'try' without any 'catches' or 'finally' clauses now becomes legal Java even if there are no initializers present. If another keyword had been chosen, these would not even have been issues that the proposal needed to address.

I rather liked the suggestion that various people put forth a while ago for using the 'protected' keyword instead. Since it seems that it is not going to be used for a lock-related proposal anymore, why not use it for this?

protected (FileReader in = new FileReader(whatever)) {
    doStuff(in.read());
}

This would provide a nice, orthogonal feature that would be unrelated to the 'try' keyword, thus making for two smaller simpler features (protected and try) rather than one big complicated one (protected+try, as in the current proposal). Its syntax would also then be more analogous to the 'synchronized' keyword, which this feature is (IMHO) MUCH more semantically similar to than it is to 'try'.

Like 'synchronized' (at least as far as closing/unlocking of resources go), ARM is a higher-level interface to a subset of functionality that is usually performed using try..finally. I don't think that the fact that closing of resources is usually IMPLEMENTED using 'try' is a good reason to make its INTERFACE syntactically similar to a try..catch..finally block, which has very different semantics.

>The revision says this:
>
>* 4. Does the construct support resource types whose termination method has
>a name other than **close**?*
>
>Not directly. There are two obvious ways in which the construct could have
>supported this functionality. One is to designate the termination method
>with an annotation. But this violates the rule that annotations cannot
>change the semantics of a program (JLS §9.7). The other is to use an actual
>modifier to denote the termination method (the finally keyword has been
>proposed for this purpose). This would, however, require a class file format
>change, and a mandate changes to tools such as JavaDoc. The resulting
>construct would be more “magical,” and wouldn't mesh as well with Java’s
>type system.

This is my number one gripe with the current proposal: It's not extensible due to its dependence on the AutoCloseable interface and the need for a method named "close()". It's too specific to files. If I want to use it for locks, for disposeable objects, or for other similar kinds of resources, I have to jump through weird hoops and use adapter classes and so forth, or settle for my 'unlock' method being called 'close' just to fit the interface. Not Good. This is true even if I am coding up my own lock objects (with a 'lock()' method that returns 'this') rather than using those provided by java.util.concurrent.

There is a better solution to this problem, which avoids the technical issues that have been raised above against annotations and the 'finally' keyword. It is possible to combine these approaches to get the desired behavior (a type-system-independent way to designate a closing method) without forcing a change to the class file format.

In fact there is already an existing precedent for doing this: Enums.

When the compiler compiles an 'enum' declaration, it generates a hidden annotation on the generated class, which identifies it as an enumerated type. This has implications for the compiler (such as the fact that another class can't extend such a type). Thus, the "annotations don't change program semantics" rule is being slightly bent in this case, but its spirit is preserved, since the annotation can't be used or even seen by ordinary users -- just by the compiler, and by JDK methods like "Class.isEnum".

In the ARM proposal, we could do the same thing with the 'finally' keyword. If the compiler notices that the 'finally' keyword is present on a method, it can supply a hidden annotation on that method which qualifies it within the class file, so that this:

public finally void dispose(); // or unlock(), or...

desugars to this:

@AutoClose public void dispose();

with the @AutoClose annotation being invisible to normal users of Java, in the same way as the annotation used to declare enum types currently is.

Some further restrictions would be needed:
1) It is a compile-time error if the 'finally' keyword appears on more than one method within a type declaration.
2) It is a compile-time error if a type extends multiple supertypes with 'finally' methods which have incompatible signatures, but does not designate one of its own methods with the "finally" keyword. (This guarantees uniqueness of finally methods).

With these rules in place, when attempting to compile an instance of the ARM construct, the compiler can then search up the inheritance tree for the first instance of a method with the @AutoClose annotation, and use that method it if it's present (or else provoke a compiler error: "this type has no finally method").

As far as I can see, the above approach is much less limiting than designating a magic interface (or pre-specified set of interfaces) such as AutoCloseable. It will make it much easier for existing classes and interfaces to be retrofitted to support ARM, since it won't affect their inheritance hierarchies.

Note that it is still possible to define a class like AutoCloseable in this scenario, should someone still want to do so:

interface AutoCloseable {
    finally void close() throws Exception;
}

Derek




More information about the coin-dev mailing list