some experiments with Concise Method Bodies
Stuart Marks
stuart.marks at oracle.com
Mon Oct 15 18:39:49 UTC 2018
When I first saw the CMB proposal, I immediately thought "delegation" and so I
thought it would be useful to see how CMB could be applied in this area. The
example I chose was the implementation class for
Collections.unmodifiableCollection(), which returns an instance of a wrapper
class [1] that delegates most (but not all) method calls to the backing collection.
I cloned the amber repo and updated to the concise-method-declarations branch.
Building the JDK from this branch worked smoothly. Compiling "Hello, world" worked:
public class Concise {
public static void main(String[] args)
-> System.out.println("Hello, concise method bodies!");
}
I took Collections$UnmodifiableCollection and extracted it into a standalone
class UnmodColl1.java [2] and stripped out some extraneous stuff. I then copied
it to UnmodColl2.java [3] and converted it to use CMB. Along the way I made some
shortening transformations that didn't directly relate to CMB, so I retrofitted
them back to UnmodColl1.java. The results are more-or-less diff-able.
Observations:
* I was able to use CMB for almost everything. These wrapper classes consistent
almost entirely of one-liners, to which CMB can be directly applied.
* The CMB version is a bit prettier. Lack of 'return' and braces reduces visual
clutter, and in a few cases it enabled things to be moved onto a single line,
reducing vertical space.
* Note that these wrapper classes already bend the usual style rules for braces
and statements of a method body, e.g., even in the original we have the method
public int size() {return c.size();}
written on a single line. If the style were followed strictly, this would be
written on three lines. There's a reason for this; writing all these one-liners
in standard style would waste an egregious amount of vertical space. CMB syntax
provides a much bigger win over standard style than over the non-standard,
compact style used in these Collections wrapper classes.
On the other hand, while the non-standard, compact style does save vertical
space, it's kind of annoying to deal with, because it's non-standard. In my
opinion the tradeoff is in favor of the compact style. But CMB mostly relieves
us of having to make this tradeoff at all.
* None of these methods have javadoc, since this is a private implementation class.
* Many of the delegating methods can be written using the method reference form:
public int size() = c::size;
This would produce a marginal improvement in the syntax, mostly by removing the
need for a set of parens and the need to pass parameters explicitly. The issue
of time-of-evaluation of the receiver mostly doesn't arise here, since the
delegate is a final field initialized by the constructor.
* I tried to use CMB for the constructor, but I got an error message saying it
was disallowed. No great loss, and makes some sense, I guess.
* Many of the methods throw exceptions. I had a bit of a wrestling match with
this. My first attempt was
UnsupportedOperationException uoe() -> new UnsupportedOperationException;
public boolean add(E e) -> throw uoe();
But this doesn't work, because 'throw' isn't an expression, and the concise body
is required to be an expression of the right type even though we know the method
can never return normally. Of course, one can do this:
public boolean add(E e) { throw uoe(); }
but I wanted to use CMB. My next attempt was this:
boolean throwUOE() { throw new UnsupportedOperationException(); }
public boolean add(E e) -> throwUOE();
Now this works, but it's a hack. Most of the throwing methods happen to return
boolean, so I was able to declare the helper method to return boolean as well.
It can also be used for void-returning methods. But if I had needed to delegate
several throwing methods that had different return types, I wouldn't be able to
do this.
I tried to use the method reference form to deal with the throwing methods, but
that didn't work, since those methods all take different parameters.
It would be nice if I could just do
public boolean add(E e) -> throw new UnsupportedOperationException();
or
public boolean add(E e) -> throw uoe();
where uoe() returns an exception instance.
**
Summary: it was fun playing with this feature. It seems to clean things up a
little bit, and for classes that are all one-liner methods the little savings
add up to a lot. The savings are incrementally greater compared to a
hypothetical wrapper class that uses the standard coding style. CMB doesn't seem
to be a must-have feature, at least not yet, but it does seem to have some
potential.
s'marks
[1]
http://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/util/Collections.java#l1021
[2] http://cr.openjdk.java.net/~smarks/amber/UnmodColl1.java
[3] http://cr.openjdk.java.net/~smarks/amber/UnmodColl2.java
More information about the amber-dev
mailing list