WARNING: This blog entry was imported from my old blog on blogs.sun.com (which used different blogging software), so formatting and links may not be correct.
I was struggling with a problem today, and I found a pretty decent workaround. Since initial googling hadn't turned anything useful up, I figured this writeup might help others who search for it in the future.
In essence, the problem boils down to "How can you initialize your own object before the superclass constructor has run?"
Let's express this in code - I've changed the example to a simple domain to make the intent obvious:
/** Represents a building, such as a house, a garage, etc. */
public class Building {
public Building() {
// Do something with the result of getColor(), such as
reservePaint(getColor());
}
protected abstract Color getColor();
}
The above class is the "framework" class I'm trying to extend. I do not have the option of changing it or extending something else; this is the integration point.
Note the error above: the constructor is calling an overridden method. This is a
well
documented problem. Previous uses of the above class had probably not run into it, because most usages would be something like the following:
public class Skyscraper extends Building {
protected Color getColor() {
return Color.LIGHT_GRAY;
}
}
Since the overridden method typically returns a constant that is independent of the subclass object being properly constructed, calls from the superclass constructor did not cause problems.
The problem was that in my case, I'm dynamically constructing many different types of Buildings, so I want to parameterize the getColor()
method:
public class PaintedGarage extends Building {
private Color color;
public PaintedGarage(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
Unsurprisingly, this fails at runtime.
(When you construct the PaintedGarage
object, the superclass constructor, Building()
, will be called before the PaintedGarage
constructor is called, and when it
calls the getColor()
method, it will read the color
field which has not yet
been initialized.)
With that long motivation: What can we do about this? Following the Effective Java practice of not calling overridden methods from the constructor is not an option. Remember, that is done in the superclass, and that is a class we have no control over - it's part of the framework we are plugging into.
In an earlier version of this, I was already computing classes on the fly (using a byte code generator library), so it was simple for me to simply inline the literal return value in an overridden version of getColor
.
However, I no longer need to do that for other purposes, so is there a way for me to ensure that getColor
gets initialized by the time the super class constructor calls it?
Yes!
The trick is to use an inner class. The process is as follows.
First, file a bug against the framework you are using to get the constructor behavior changed.
Second, try something like this:
public class PaintedGarageFactory {
private Color color; // Moved out of PaintedGarage and into enclosing (not super!) class
private PaintedGarageFactory(Color color) { // Use factory method below
this.color = color;
}
class PaintedGarage extends Building {
public PaintedGarage() {
}
public Color getColor() {
return color;
}
}
public static PaintedGarage create(Color color) {
PaintedGarageFactory factory = new PaintedGarageFactory(color);
// Notice how the outer class is fully constructed before the
// inner class (and its superclass constructor) is built.
// Each factory instance constructs PaintedGarages of a particular
// color (and could be reused etc.)
return factory.create();
}
private PaintedGarage create() {
return PaintedGarage();
}
}
What this has done is move the parameters that need to be initialized before the superclass
constructor is run, out to the outer class. These are obviously initialized before the innerclass
is constructed.
This is not a good way to write code, but it solves the immediate need: You've
provided an implementation of the super class that is properly constructed as far as the
superclass constructor is concerned.
Don't forget to get the framework fixed.
Hi Tor,
ReplyDeleteI read your article https://bpcatalog.dev.java.net/nonav/ajax/jsf-ajax/ and I have some doubts...
You said that there are three ways to integrate Ajax and JSF...
All the three don't use JSF life cycle?
The first way use RenderPhaseListener, when this is execute?
Write to dmarafon@gmail.com if you could help me! thanks a lot