Java Specialists' Java Training Europehome of the java specialists' newsletter

The Java Specialists' Newsletter
Issue 0862004-03-19 Category: Tips and Tricks Java version:

GitHub Subscribe Free RSS Feed

Initialising Fields before Superconstructor call

by 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!

NEW: Please see our new "Extreme Java" course, combining concurrency, a little bit of performance and Java 8. Extreme Java - Concurrency & Performance for Java 8.

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)
  1. main(String[]) constructs a CustomerView1
  2. Before CustomerView1 has initialised any variables, it calls super(owner) [note that the type field has not been initialised yet]
  3. 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:

  1. the other developers are aware of what I've had to do,
  2. the source code is generously documented with useful comments,
  3. 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

Java Master
Java Concurrency
Design Patterns
In-House Courses



© 2010-2014 Heinz Kabutz - All Rights Reserved Sitemap
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. JavaSpecialists.eu is not connected to Oracle, Inc. and is not sponsored by Oracle, Inc.
@CORE_THE_BAND #RBBJGR