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 2
Page 2 of 4

The <init> method is not actually part of the Java language. Rather, it is something the Java virtual machine expects to see in a Java class file. This distinction is significant because the Java language does not depend on the class file. Java source can be compiled into other binary formats, including native executables. A Java compiler that translates Java language source into some other binary format need not generate a method named <init>, so long as objects are initialized in the proper way at the proper time. The Java Language Specification (JLS) details the order of initialization and when it occurs, but doesn't say how it is actually accomplished. (See the Resources section for information on the Java Language Specification.) Still, understanding how initialization works inside class files can help you understand the order of initialization in the language.

Initializers

Besides providing constructors, Java offers one other way for you to assign an initial value to instance variables: initializers. As mentioned previously, the two kinds of initializers in Java are instance variable initializers and instance initializers.

Instance variable initializers

In a constructor, you have the freedom to write as much code as needed to calculate an initial value. In an instance variable initializer, you have only an equals sign and one expression. For example, if you wanted to always start coffee cups out with 355 milliliters of fresh brewed coffee in them, you could initialize innerCoffee with a constructor:

// In source packet in file init/ex9/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee;
    public CoffeeCup() {
        innerCoffee = 355;
    }
    // ...
}

Alternatively, you could initialize innerCoffee with an instance variable initializer:

// In source packet in file init/ex10/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee = 355; // "= 355" is an initializer
    // no constructor here
    // ...
}

The right-hand side of the equals sign in an initializer can be any expression that evaluates to the type of the instance variable.

Instance initializers

Java 1.1 introduced the instance initializer, which is also called the instance initialization block. Here is the same CoffeeCup class with its innerCoffee variable initialized by an instance initializer:

// In source packet in file init/ex19/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee;
    // The following block is an instance initializer
    {
        innerCoffee = 355;
    }
    // no constructor here
    // ...
}

This manner of initializing innerCoffee yields the same result as the previous two examples: innerCoffee is initialized to 355.

Instance initializers are a useful alternative to instance variable initializers whenever: (1) initializer code must catch exceptions, or (2) perform fancy calculations that can't be expressed with an instance variable initializer. You could, of course, always write such code in constructors. But in a class that had multiple constructors, you would have to repeat the code in each constructor. With an instance initializer, you can just write the code once, and it will be executed no matter what constructor is used to create the object. Instance initializers are also useful in anonymous inner classes, which can't declare any constructors at all.

The code inside an instance initializer may not return. Except in the case of anonymous inner classes, an instance initializer may throw checked exceptions only if the checked exceptions are explicitly declared in the throws clause of every constructor in the class. Instance initializers in anonymous inner classes, on the other hand, can throw any exception.

Initializers can't make forward references

When you write an initializer (either an instance variable initializer or instance initializer), you must be sure not to refer to any instance variables declared textually after the variable being initialized. In other words, you can't make a forward reference from an initializer. If you disobey this rule, the compiler will give you an error message and refuse to generate a class file. When an object is created, initializers are executed in textual order -- their order of appearance in the source code. This rule helps prevent initializers from using instance variables that have yet to be properly initialized.

For example, here is a virtual café class that has four chairs for every table:

// In source packet in file init/ex11/VirtualCafe.java
class VirtualCafe {
    private int tablesCount = 20;
    private int chairsCount = 4 * tablesCount;
    //...
}

These initializers work fine. The chairsCount initializer, = 4 * tablesCount, refers to an instance variable declared textually before it, so the compiler is happy. Because initializers are executed in textual order, tablesCount is already initialized to 20 by the time chairsCount's initializer multiplies it by four. Thus, chairsCount is initialized to 80.

If you were able to use instance variables declared textually later, you could end up with unexpected behavior:

// In source packet in file init/ex12/VirtualCafe.java
// THIS WON'T COMPILE, BUT AS A THOUGHT EXPERIMENT,
// IMAGINE IT WERE POSSIBLE
class VirtualCafe {
    private int chairsCount = 4 * tablesCount;
    private int tablesCount = 20;
    //...
}

If the above declaration were possible, chairsCount's initializer would use tablesCount before tablesCount were assigned a value of 20. At that point, the tablesCount variable would have its default initial value of zero. Hence, this code would initialize chairsCount to four times zero. If you do the math, you will discover that, in this case, chairsCount does not initialize to 80.

Getting around the forward reference rule

Although this kind of forward referencing is disallowed by the compiler in an attempt to help programmers avoid just the above kind of mistake, you can't let down your guard completely. There is still a way you could inadvertently (or purposefully) circumvent the compiler's preventative restrictions:

// In source packet in file init/ex13/VirtualCafe.java
class VirtualCafe {
    private int chairsCount = initChairsCount();
    private int tablesCount = 20;
    private int initChairsCount() {
        return tablesCount * 4;
    }
    //...
}

The above code compiles fine, and has the same result as the previous thought experiment. Here chairsCount's initializer sneakily invokes a method that uses tablesCount before its initializer has been executed. When initChairsCount() calculates tablesCount * 4, tablesCount is still at its default initial value of zero. As a result, initChairsCount() returns zero, and chairsCount is initialized to zero.

Initialization and inheritance

When an object is initialized, all the instance variables defined in the object's class must be set to proper initial values. While this is necessary, often it is not enough to yield a fully initialized class. An object incorporates not only the fields explicitly declared in its class, but also those declared in its superclasses. To fully initialize an object, therefore, all instance variables declared in its class and in all its superclasses must be initialized.

Instance data of objects

Every object, except class Object itself, has at least one superclass. When an object is created, the Java virtual machine allocates enough space for all the object's instance variables, which include all fields defined in the object's class and in all its superclasses. For example, consider the following classes:

// Declared in file Object.java (not In source packet)
package java.lang;
public class Object {
    // Has no fields
    // Has several methods, not shown...
}
// In source packet in file init/ex14/Liquid.java
class Liquid {
    // Has two fields:
    private int mlVolume;
    private float temperature; // in Celsius
    // Has several methods, not shown...
}
// In source packet in file init/ex14/Coffee.java
class Coffee extends Liquid {
    // Has two fields:
    private boolean swirling;
    private boolean clockwise;
    // Has several methods, not shown...
}
Figure 1. Class Coffee 's superclasses and fields

You can see the inheritance hierarchy for class Coffee, as defined above, in Figure 1. This figure, as well as the code above, shows Object as having no instance variables. But it is possible that Object could have instance variables. The actual internal make-up of class Object is a detail specific to each Java platform implementation. It is extremely likely, however, that Object will have no fields in any given Java platform implementation. Because Object is the superclass of all other objects, any fields declared in Object must be allocated for every object used by every Java program.

In Figure 2, you can see the data that must be allocated on the heap for a Coffee object. The part of the heap that is occupied by the instance data for the Coffee object is shown in the cyan color. Keep in mind that the actual manner of representing objects on the heap is an implementation detail of each particular Java virtual machine. This figure represents just one of many possible schemes for storing objects on the heap inside the JVM.

Figure 2. Instance data for a Coffee object

Figure 2 shows that the instance data for a Coffee object includes each instance variable defined in class Coffee and each of Coffee's superclasses. Both of Liquid's fields, mlVolume and temperature, are part of the Coffee object's data, as well as Coffee's fields: swirling and clockwise. This is true even though Coffee doesn't actually inherit the mlVolume and temperature fields from class Liquid.

A note on the word "inherit"

In Java jargon, the word "inherit" has a restricted meaning. A subclass inherits only accessible members of its superclasses -- and only if the subclass doesn't override or hide those accessible members. A class's members are the fields and methods actually declared in the class, plus any fields and methods it inherits from superclasses. In this case, because Liquid's mlVolume and temperature fields are private, they are not accessible to class Coffee. Coffee does not inherit those fields. As a result, the methods declared in class Coffee can't directly access those fields. Despite this, those fields are still part of the instance data of a Coffee object.

Pointers to class data

Figure 2 also shows, as part of the instance data of the Coffee object, a mysterious 4-byte quantity labeled "native pointer to class information." Every Java virtual machine must have the capability to determine information about its class, given only a reference to an object. This is needed for many reasons, including type-safe casting and the instanceof operator.

Figure 2 illustrates one way in which a Java virtual machine implementation could associate class information with the instance data for an object. In this figure, a native pointer to a data structure containing class information is stored along with the instance variables for an object. The details in which the various ways a JVM could connect an object's data with its class information are beyond the scope of this article. The important thing to understand here is that class information will in some way be associated with the instance data of objects, and that the instance data includes fields for an object's class and all its superclasses.

Initializing fields in superclasses

Each class contains code to initialize the fields explicitly declared in that class. Unlike methods, constructors are never inherited. If you don't explicitly declare a constructor in a class, that class will not inherit a constructor from its direct superclass. Instead, the compiler will generate a default constructor for that class. This is because a superclass constructor can't initialize fields in the subclass. A subclass must have its own constructor to initialize its own instance variables. In the class file, this translates to: every class has at least one <init> method responsible for initializing the class variables explicitly declared in that class.

For every object, you can trace a path of classes on an inheritance hierarchy between the object's class and class Object. For the Coffee object described above and shown in Figures 1 and 2, the path is: Coffee, Liquid, Object. To fully initialize an object, the Java virtual machine must invoke (at least) one instance initialization method from each class along the object's inheritance path. In the case of Coffee, this means that at least one instance initialization method must be invoked for each of the classes Coffee, Liquid, and Object.

During initialization, an <init> method may use one field in calculating another field's initial value. While this is perfectly reasonable, it brings up the possibility that a field could be used before it has been initialized to its proper (not default) initial value. As mentioned earlier in this article, Java includes mechanisms that help prevent an instance variable from being used before it has been properly initialized. One mechanism is the rule, enforced by the Java compiler, forbidding initializers from directly using instance variables declared textually after the variable being initialized. Another mechanism is the order in which the fields from each class along an object's inheritance path are initialized: the "order of initialization."

Order of initialization

In Java, the fields of an object are initialized starting with the fields declared in the base class and ending with the fields declared in the object's class. For a CoffeeCup object with the inheritance path shown in Figure 1, the order of initialization of fields would be:

  1. Object's fields (this will be quick, because there are none)
  2. Liquid's fields (mlVolume and temperature)
  3. Coffee's fields (swirling and clockwise)

This base-class-first order aims to prevent fields from being used before they are initialized to their proper (not default) values. In a constructor or initializer, you can safely use a superclass's field directly, or call a method that uses a superclass's field. By the time the code in your constructor or initializer is executed, you can be certain that the fields declared in any superclasses have already been properly initialized.

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