Abstract: We cannot execute any code before calling the superclass constructor. Or can we? Read more to find out.
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!
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
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)
type
field has not been initialised yet]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.
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:
Have fun with this approach, and amaze your colleagues ;-)
Kind regards
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.