Classic Tutorials for Java Beginners

Object initialization in Java

The full story of object initialization in the Java language and virtual machine

1 2 3 4 Page 3
Page 3 of 4

For example, you could safely use the temperature variable declared in class Liquid when you are initializing the swirling variable declared in class Coffee. (Perhaps if the temperature is above the boiling point for coffee, you set swirling to false.) If temperature were not private, class Coffee would inherit the field, and you could use it directly in an initializer or constructor of class Coffee. In this case, temperature is private, so you'll have to use the temperature field indirectly, through a method:

// In source packet in file init/ex15/Liquid.java
class Liquid {
    private int mlVolume;
    private float temperature; // in Celsius
    public Liquid() {
        mlVolume = 300;
        temperature = (float) (Math.random() * 100.0);
    }
    public float getTemperature() {
        return temperature;
    }
    // Has several other methods, not shown...
}
// In source packet in file init/ex15/Coffee.java
class Coffee extends Liquid {
    private static final float BOILING_POINT = 100.0f; // Celsius
    private boolean swirling;
    private boolean clockwise;
    public Coffee(boolean swirling, boolean clockwise) {
        if (getTemperature() >= BOILING_POINT) {
            // Leave swirling at default value: false
            return;
        }
        this.swirling = swirling;
        if (swirling) {
            this.clockwise = clockwise;
        } // else, leave clockwise at default value: false
    }
    // Has several methods, not shown,
    // but doesn't override getTemperature()...
}

In the example, the constructor for Coffee invokes getTemperature() and uses the return value in the calculation of the proper initial value of swirling and clockwise. getTemperature() returns the value of the temperature variable; thus, the constructor for Coffee uses a field declared in Liquid. This works because, by the time the code inside Coffee's constructor is executed, the instance variables declared in Liquid are guaranteed to have already been initialized to their proper starting values.

The structure of <init>

How does Java ensure the correct ordering of initialization? By the manner in which the Java compiler generates the instance initialization method. Into each <init> method, the compiler can place three kinds of code:

  1. An invocation of another constructor
  2. Instance variable initializers
  3. The constructor body

The order in which the compiler places these components into the <init> method determines the order of initialization of an object's fields.

(Almost) every constructor's first act

For every class except Object, the first thing each <init> method will do is invoke another constructor. If you included a this() invocation as the first statement in a constructor, the corresponding <init> method will start by calling another <init> method of the same class. For example, for the following class:

// In source packet in file init/ex4/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee;
    public CoffeeCup() {
        this(237); // Calls other constructor
        // Could have done more construction here
    }
    public CoffeeCup(int amount) {
        innerCoffee = amount;
    }
    // ...
}

The <init> method for the default constructor would first invoke the <init> method for the constructor, which takes an int parameter, passing it 237.

Automatic invocation of super()

For any class except class java.lang.Object, if you write a constructor that does not begin with a this() invocation, the <init> method for that constructor will begin with an invocation of a superclass constructor. You can explicitly invoke a superclass constructor using the super() statement. If you don't, the compiler will automatically generate an invocation of the superclass's no-arg constructor. (This is true for default constructors as well. With the exception of class Object, the <init> method for any default constructor will do only one thing: invoke the <init> method for the superclass's no-arg constructor.) For example, given this CoffeeCup constructor from the example above:

public CoffeeCup(int amount) {
    innerCoffee = amount;
}

The corresponding <init> method would begin with an invocation of the <init> method for Liquid's (the direct superclass's) no-arg constructor.

Alternatively, you could have included an explicit super() statement at the top of the Coffee constructor, as in:

public CoffeeCup(int amount) {
    super();
    innerCoffee = amount;
}

This version has the same effect as the previous version. If you want to invoke the superclass's no-arg constructor, you needn't provide an explicit super() invocation. The compiler will generate a no-arg super() invocation for you.

Invoking super() with arguments

If, on the other hand, you want to invoke a superclass constructor that takes parameters, you must provide an explicit super() invocation. Here's an example:

// In source packet in file init/ex16/Liquid.java
class Liquid {
    private int mlVolume;
    private float temperature; // in Celsius
    public Liquid(int mlVolume, float temperature) {
        this.mlVolume = mlVolume;
        this.temperature = temperature;
    }
    public float getTemperature() {
        return temperature;
    }
    // Has several other methods, not shown,
    // but doesn't include another constructor...
}
// In source packet in file init/ex16/Coffee.java
public class Coffee extends Liquid {
    private static final float BOILING_POINT = 100.0f; // Celsius
    private boolean swirling;
    private boolean clockwise;
    public Coffee(int mlVolume, float temperature,
        boolean swirling, boolean clockwise) {
        super(mlVolume, temperature);
        if (getTemperature() > BOILING_POINT) {
            // Leave swirling at default value: false
            return;
        }
        this.swirling = swirling;
        if (swirling) {
            this.clockwise = clockwise;
        } // else, leave clockwise at default value: false
    }
    // has several methods, not shown,
    // but doesn't override getTemperature()...
}

In this example, Coffee's constructor explicitly invokes Liquid's constructor with a super() statement. Because class Liquid explicitly declares a constructor, the Java compiler won't generate a default constructor. Moreover, because Liquid doesn't explicitly declare a no-arg constructor, class Liquid won't have a no-arg constructor at all. For this reason, had Coffee's constructor not started with an explicit super() invocation, class Coffee would not have compiled. (Given this declaration of class Liquid, a simple new Liquid() statement would not compile either. You must invoke the constructor that is available to you, as in: new Liquid(25, 50.0).) If a subclass's direct superclass does not offer a no-arg constructor, every constructor in that subclass must begin with either an explicit super() or this()invocation.

Only one constructor invocation allowed

Note that you can't have both this() and super() in the same constructor. You can only have one or the other (or neither, if the direct superclass includes a no-arg constructor). If a constructor includes a this() or super() invocation, it must be the first statement in the constructor.

Catching exceptions not allowed

One other rule enforced on constructors is that you can't catch any exceptions thrown by the constructor invoked with this() or super(). To do so, you would have to begin your constructor with a try statement:

// In source packet in file init/ex17/Coffee.java
// THIS WON'T COMPILE, BECAUSE THE super() INVOCATION
// DOESN'T COME FIRST IN THE CONSTRUCTOR
class Coffee extends Liquid {
    //...
    public Coffee(int mlVolume, float temperature,
        boolean swirling, boolean clockwise) {
        try {
            super(mlVolume, temperature);
        }
        catch (Throwable e) {
            //...
        }
        //...
    }
    //...
}

The point to understand here is that if any instance initialization method completes abruptly by throwing an exception, initialization of the object fails. This in turn means that object creation fails, because in Java programs, objects must be properly initialized before they are used.

The proper way to signal that an error occurred during object initialization is by throwing an exception. If an <init> method throws an exception, it is likely that at least some of the fields that <init> method normally takes responsibility for did not get properly initialized. If you were able to catch an exception thrown by an <init> method you invoked with this() or super(), you could ignore the exception and complete normally. This could result in an improperly or incompletely initialized object being returned by new. This is why catching exceptions thrown by <init> methods invoked via this() or super() is not allowed.

Inheritance and initialization order

From the many rules that surround the invocation of instance initialization methods via this() or super(), there arises a clear and certain order for instance variable initialization. Although <init> methods are called in an order starting from the object's class and proceeding up the inheritance path to class Object, instance variables are initialized in the reverse order. Instance variables are initialized in an order starting from class Object and proceeding down the inheritance path to the object's class. The reason the order of instance variable initialization is reverse to that of <init> method invocation is that the first thing each <init> method (except Object's) does is call another <init> method. So the superclass <init> method is invoked and completes before any initialization code of the current class's <init> method begins execution.

As an example of this ordering, consider again the inheritance hierarchy for class Coffee as shown in Figure 1 and the following implementation of those classes:

// In source packet in file init/ex18/Liquid.java
class Liquid {
    private int mlVolume;
    private float temperature; // in Celsius
    Liquid(int mlVolume, float temperature) {
        this.mlVolume = mlVolume;
        this.temperature = temperature;
    }
    //...
}
// In source packet in file init/ex18/Coffee.java
class Coffee extends Liquid {
    private boolean swirling;
    private boolean clockwise;
    public Coffee(int mlVolume, float temperature,
        boolean swirling, boolean clockwise) {
        super(mlVolume, temperature);
        this.swirling = swirling;
        this.clockwise = clockwise;
    }
    //...
}

When you instantiate a new Coffee object with the new operator, the Java virtual machine first will allocate (at least) enough space on the heap to hold all the instance variables declared in Coffee and its superclasses. Second, the virtual machine will initialize all the instance variables to their default initial values. Third, the virtual machine will invoke the <init> method in the Coffee class.

The first thing Coffee's <init> method will do is invoke the <init> method in its direct superclass, Liquid. The first thing Liquid's <init> method will do is invoke the no-arg <init> method in its direct superclass, Object. Object's <init> method most likely will do nothing but return, because it has no instance variables to initialize. (Once again, what Object's <init> method actually does is an implementation detail of each particular Java runtime environment.) When Object's <init> method returns, Liquids <init> method will initialize mlVolume and temperature to their proper starting values and return. When Liquids <init> method returns, Coffee's <init> method will initialize swirling and clockwise to their proper starting values and return. Upon normal completion of Coffee's <init> method (in other words, so long as it doesn't complete abruptly by throwing an exception), the JVM will return the reference to the new Coffee object as the result of the new operator.

this() won't change the order of initialization

Note that if an <init> method begins not by invoking a superclass's <init> method (a super() invocation), but instead by invoking another <init> method from the same class (a this() invocation), the order of instance variable initialization remains the same. You can have several this() invocations in a row if you wish. In other words, you could have an <init> method that invokes another with this(), and that <init> method invokes yet another with this(), and so on. But in the end, there will always be an <init> method with a super() invocation -- either an explicit super() invocation or a compiler-generated one. Since this() and super() are both always the first action a constructor takes, the instance variables will always be initialized in order from the base class on down.

In addition to the code for constructor invocations and constructor bodies, the Java compiler also places code for any initializers in the <init> method. If a class includes initializers, the code for them will be placed after the superclass method invocation but before the code for the constructor body, in every <init> method that begins with an explicit or implicit super() invocation. Code for initializers are not included as part of <init> methods that begin with a this() invocation. Because initializer code appears only in <init> methods that begin with a super() invocation, and not in those that begin with a this() invocation, the initializers for a class are guaranteed to be run only once for each new class creation. Because initializers appear after the super() invocation and before the code from the constructor's body, you can always be certain that initializers will have been run by the time any constructor code for that class is executed.

Calling subclassed methods from constructors

Related:
1 2 3 4 Page 3
Page 3 of 4