Now it's your turn
to write some code. Suppose that you have a library class called Thing
whose sole constructor takes an int parameter:
public class Thing { public Thing(int i) { ... } ... }
A Thing instance provides no way to get the value of
its constructor parameter. Because Thing is a library class, you have
no access to its internals, and you can't modify it.
Suppose that you want to write a subclass called
MyThing, with a constructor that computes the parameter to the
superclass constructor by invoking the method SomeOtherClass.func().
The value returned by this method changes unpredictably from call to call.
Finally, suppose that you want to store the value that was passed to the
superclass constructor in a final instance field of the subclass for future use.
This is the code that you'd naturally write:
public class MyThing extends Thing { private final int arg; public MyThing() { super(arg = SomeOtherClass.func()); ... } ... }
Unfortunately, it isn't legal. If you try to compile it, you'll
get an error message that looks something like this:
MyThing.java: can't reference arg before supertype constructor has been called super(arg = SomeOtherClass.func()); ^
How can you rewrite MyThing to achieve the desired
effect? The MyThing() constructor must be thread-safe: Multiple threads
may invoke it concurrently.
Solution 53: Do Your Thing
You could try to stash the result of
the invocation SomeOtherClass.func() in a static field prior to
invoking the Thing constructor. This solution is workable but awkward.
In order to achieve thread-safety, you must synchronize access to the stashed
value, which requires unimaginable contortions. Some of these contortions can be
avoided by using a thread-local static field (java.util.ThreadLocal),
but a much better solution exists.
The preferred solution is inherently thread-safe as well as
elegant. It involves the use of second, private constructor in
MyThing:
public class MyThing extends Thing { private final int arg; public MyThing() { this(SomeOtherClass.func()); } private MyThing(int i) { super(i); arg = i; } }
This solution uses an alternate
constructor invocation [JLS 8.8.7.1]. This feature allows one constructor
in a class to chain to another constructor in the same class. In this case,
MyThing() chains to the private constructor MyThing(int),
which performs the required instance initialization. Within the private
constructor, the value of the expression SomeOtherClass.func() has been
captured in the parameter i and can be stored in the final field
param after the superclass constructor returns.
The Private Constructor Capture
idiom illustrated by the solution to this puzzle is a useful pattern to add to
your bag of tricks. We've seen some genuinely ugly code that could have been
avoided with this pattern.
No comments:
Post a Comment
Your comments are welcome!