Not Your Father's Java
While reading David Astels' excellent book Test-Driven Development: A Practical Guide [1], I tripped over the following code snippet in a JUnit test:
Not your father's Java, eh?
What's going on here? On the right-hand side of the assignment, I
recognize the apparent zero-argument constructor invocation followed by a
block to be a declaration/instantiation of an anonymous inner class,
which extends Vector. The zero-argument Vector
constructor initializes the instance's Vector-ness.
We often see the anonymous inner class construct in Swing-based applications, as one-off instances of event listeners:
You might even have spotted anonymous inner class instances which implement
Runnable or extend Thread:
Usually, we create anonymous inner classes to specify behavior in response
to a method call; that is, we provide an overridden implementation of one or
more methods declared in the implemented interface or extended superclass.
So what's the deal with the anonymous Vector subclass above? It
doesn't refine any method behavior from Vector at all! What's up
with the extra set of curly braces, with calls to add() made
within?
We have, my friends, discovered Java's instance initializer. [2]
Remember our old buddy the static initializer? You know--those blocks you can use to initialize static fields or perform other initialization tasks after the JVM loads the containing class?
Well, instance initializers are similar. Like static initializers, instance initializers are executed in the order in which they are declared in source. Whereas static initializers are executed on class initialization, instance initializers are executed when an instance of the declaring class is constructed--just after the applicable superclass constructor, but before the applicable declaring-class constructor.
It turns out that the original intents of instance initializers were 1) to
provide a means for initializing instance fields lexically near to the
declaration of those fields, as opposed to with = or
a constructor parameter; and 2) to allow instances of anonymous inner classes
to initialize themselves by executing arbitrary code. However, in my nearly
seven years of writing and reading Java code, I hadn't once encountered an
instance initializer. I had to refer to the JLS [3] to decipher the code snippet.
So we have an anonymous inner class with an instance initializer that calls
its add() method thrice.
Don't Repeat Yourself
This got me thinking: Why bring two relatively obscure Java constructs together in this unholy matrimony? What's wrong with:
Nothing's wrong with it per se; it certainly gets the job done. Dave's method, obscure though it may be, has some advantages.
Concision
Java's syntax, by curly-braced-language standards, is relatively clean--a tame amount of reserved words, very little cartoon-curse-word punctuation, and so forth. However, sometimes we find ourselves typing a lot more than we might like:
These kinds of gymnastics often leave me wishing I'd have been a Smalltalk programmer: [4]
I believe that Dave's method betrays a yearning for the simplicity of the last Smalltalk construct above. Hey, why not? Why repeat the receiver over and over if you don't have to? The big reason in Java, of course, is that the instance initializer is obscure. Once you learn the idiom, however, it is admittedly a great way to reduce typing and redundancy without sacrificing too much in the way of clarity. More on this later...
(I'm going to stay out of the which-language-is-more-readable debate, and instead say that there is plenty of help for curly-brace-language devotees who find Smalltalk confusing.[5])
Emphasis on one-offness
Anonymous inner classes are used for one-off objects that implement
behavior from an interface, or extend a class to provide special behavior (in
this case, initialization behavior). In this sense, Dave's construct works
great: By using an anonymous inner class extension of Vector, he
emphasizes that there isn't anything particularly special about the list.
It's just a collection of some arbitrary movie names for a test to chew on,
once and only once.
Java makes anonymous inner classes available at the cost of generating an extra class at compile time:
In JUnit tests, this is of no great consequence. However, in production code, I wouldn't sprinkle such one-offs around willy-nilly, especially not simple lists.
The making of an idiom
Now, some might say that iterating over a Java collection like so:
is no less expressive than (Smalltalk):
It's about as concise as one can get using just the core Java collections API. It seems to be the prevailing idiom. One language's idioms do not necessarily translate so cleanly into another language--to create self-iterating collections in Java, one would introduce block-type interfaces, typically implemented by anonymous inner classes, not the most beautiful of Java syntactic elements. [6]
Just as with natural language, learning the idioms of a programming
language represents important milestones on the road to fluency. Indeed, just
as with natural language, one doesn't really "know" the language until one
begins absorbing the language's idioms. How does an idiom become an idiom?
Programming language idioms spread throughout a developer community via the
Internet, in books, by word of mouth, and so forth. Certain constructs and
expressions over time become commonly recognized and thus the de
facto ways of performing given tasks. Over time we "chunk" the construct
and recognize it as a whole without having to parse its parts on every read.
So for Java programmers, the for-loop-Iterator idiom becomes
the expressive way to enumerate a collection.
The prevailing idiom for initializing a Java list seems to be to
instantiate an empty list, and to call add() N times, with N
statements, repeating the receiver. Might the
anonymous-inner-class-plus-instance-initializer construct be an idiom in the
making, or is it, like self-iterating collections, an attempt to inject
Smalltalk sensibilities into a language that resists it? If I had been
steeped more deeply in Java community lore, would I have flinched at the
construct? Has the Java community observed and subsequently rejected the
idiom, or are we witnesses to an idiom's nascent acceptance?
Stay tuned...