|
The Java Specialists' Newsletter
Issue 086 2004-03-19
Category:
Tips and Tricks
Java version: Initialising Fields before Superconstructor callby Dr. Heinz M. Kabutz
Welcome to the 86th edition of The Java(tm) Specialists' Newsletter. My last newsletter
caused some interesting reactions. A client told me that her
programmers got quite excited talking about pushups, then
decided that this could be better discussed at the local
pub. The rest of the afternoon was spent drinking beers.
Not quite the reaction I had hoped for, but at least I got
them th(dr)inking!
Upcoming Java Specialist Master Courses:
- please click here to sign up.
As from May 2010, we are also offering this course on the island of Crete. We
only accept 6 students per class in Crete, due to the size of our conference
room. Please book early to avoid disappointment!
San Jose CA, Mar 16-19 2010, $3500 Ottawa, Canada, Mar 22-25 2010, $3500 Oslo, Norway, Apr 13-16 2010, Kr 24500 Montreal, Canada, Apr 20-23 2010, $3500 Toronto, Canada, May 17-20 2010, $3500 Chania, Crete, May 25-28, Jun 29-Jul 2 or Aug 24-27 2010, €2500
In-house courses if these dates or locations do not suit you - click here for more information. Initialising Fields before Superconstructor call
One of the biggest surprises that I encountered when I first
learned Java was when Bruce Eckel pointed out in his book that when
a method is called from the superconstructor, the most
derived method is invoked.
The fields in the subclass are not yet initialised, which can
lead to nasty bugs.
I have written about this phenomenon before, and my message
was always: "Don't call non-private methods from your
constructors!"
But what do you do if you are working within a framework, and
your superclass has this behaviour?
In a recent project, I wanted to have the class take as a
parameter a business object class, and depending on the type,
create a different GUI Form, via some FormFactory. It looked
something like this:
import javax.swing.*;
public abstract class FormView1 {
private final JComponent mainUIComponent;
public FormView1(JFrame owner) {
// do some stuff...
mainUIComponent = makeUI();
}
public abstract JComponent makeUI();
public JComponent getMainUIComponent() {
return mainUIComponent;
}
}
import javax.swing.*;
public class CustomerView1 extends FormView1 {
private final Integer type;
public CustomerView1(JFrame owner, int type) {
super(owner);
this.type = new Integer(type);
}
public JComponent makeUI() {
switch (type.intValue()) {
case 0:
return new JTextArea();
case 1:
return new JTextField();
default:
return new JComboBox();
}
}
public static void main(String[] args) {
CustomerView1 view1 = new CustomerView1(null, 1);
System.out.println(view1.getMainUIComponent().getClass());
}
}
What happens when you run that code? To my newsletter
readers, the answer will be obvious: NullPointerException!
The sequence of calls is as follows:
java.lang.NullPointerException
at CustomerView1.makeUI(CustomerView1.java:13)
at FormView1.<init>(FormView1.java:9)
at CustomerView1.<init>(CustomerView1.java:8)
at CustomerView1.main(CustomerView1.java:24)
- main(String[]) constructs a CustomerView1
- Before CustomerView1 has initialised any variables,
it calls super(owner) [note that the
type
field has not been initialised yet]
- FormView1 makes a call to makeUI() which uses the type
field before it has been initialised.
Ok, this is nothing new. People have been writing about this
problem for many years.
A good solution to this problem would be to change the
framework. For example, you could change the FormView to
take parameters in the constructor, which it then passes to the
makeUI() method, like so:
import javax.swing.*;
public abstract class FormView2 {
private final JComponent mainUIComponent;
public FormView2(JFrame owner, Object subclassParameters) {
// do some stuff...
mainUIComponent = makeUI(subclassParameters);
}
public abstract JComponent makeUI(Object subclassParameters);
public JComponent getMainUIComponent() {
return mainUIComponent;
}
}
import javax.swing.*;
public class CustomerView2 extends FormView2 {
private Integer type;
public CustomerView2(JFrame owner, int type) {
super(owner, new Integer(type));
}
public JComponent makeUI(Object params) {
this.type = (Integer) params;
switch (type.intValue()) {
case 0:
return new JTextArea();
case 1:
return new JTextField();
default:
return new JComboBox();
}
}
public static void main(String[] args) {
CustomerView2 view1 = new CustomerView2(null, 1);
System.out.println(view1.getMainUIComponent().getClass());
}
}
It should be obligatory to put an Object parameter in all
methods that are called from a constructor in a class that is
meant to be overridden. At least that way we can
configure the class.
What if you cannot change the framework?
Let's assume that you cannot change the framework (or that
you just do not want to). What options do you have?
The first option that springs to mind is inheritance.
Instead of having one CustomerView, we make three different
ones. Each of the three CustomerView classes will return a
different component.
However, this could potentially cause too many classes to be
created. Having many classes adds to your maintenance
headache.
The second option is one that I discovered this week, much to
my joy. In your call to super(), you cannot call
any non-static methods of your class, because this
has not been initialised yet. However, you may
call static methods. So, provided that the superclass takes
at least one parameter, we can write the following hack:
import javax.swing.*;
public class CustomerView3 extends FormView1 {
private static final Object lock = new Object();
private static Integer tempType;
private Integer type;
/** The problem with the superclass is that it makes a callback
* to method makeUI(). We however want to set a variable in
* this object before that method is called. The way we do it
* is to set a static variable, then at the beginning of the
* makeUI() method we initialise our non-static variable. */
public CustomerView3(JFrame owner, int type) {
super(hackToPieces(owner, type));
}
private static JFrame hackToPieces(JFrame owner, int type) {
synchronized (lock) {
/** We want to prevent several threads overwriting the
* tempType static variable. */
while (tempType != null) {
try {
lock.wait();
} catch (InterruptedException e) {
// someone wants to shut us down, let's return and keep
// the thread interrupted
Thread.currentThread().interrupt();
return owner;
}
}
tempType = new Integer(type);
return owner;
}
}
/** We initialise the variables and set the temporary static
* fields to null. */
private void init() {
synchronized (lock) {
type = tempType;
tempType = null;
lock.notifyAll();
}
}
public JComponent makeUI() {
// Make sure that init() is called first. This assumes that
// makeUI only gets called once, by the superclass'
// constructor. If that is a false assumption, you will have
// to be a bit cleverer here. Probably test whether type
// is null or something. Exercise for the reader.
init();
switch (type.intValue()) {
case 0: return new JTextArea();
case 1: return new JTextField();
default: return new JComboBox();
}
}
public static void main(String[] args) {
CustomerView3 view1 = new CustomerView3(null, 1);
System.out.println(view1.getMainUIComponent().getClass());
}
}
It was a great thrill to solve this problem to which I did
not know a solution.
The "hack" in CustomerView3 is perfectly legitimate Java code,
so I do not have a problem using it in production code,
provided that:
- the other developers are aware of what I've had to do,
- the source code is generously documented with useful comments,
- this approach was easier than just modifying the framework.
Have fun with this approach, and amaze your colleagues ;-)
Kind regards
Heinz
Tips and Tricks Articles
Related Java Course
|