Object initialization in Java

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

An object is a chunk of memory bundled with the code that manipulates memory. In the memory, the object maintains its state (the values of its instance variables), which can change and evolve throughout its lifetime. To get a newly-created object off to a good start, its newly-allocated memory must be initialized to a proper initial state. This article is a companion piece to this month's Design Techniques installment, which focuses on designing classes for proper initialization. Here we take an in-depth look at the mechanisms Java uses to manage object initialization.

The motivation behind Java's initialization mechanisms

At the beginning of an object's life, the Java virtual machine (JVM) allocates enough memory on the heap to accommodate the object's instance variables. When that memory is first allocated, however, the data it contains is unpredictable. If the memory were used as is, the behavior of the object would also be unpredictable. To guard against such a scenario, Java makes certain that memory is initialized, at least to predictable default values, before it is used by any code.

Initialization is important because, historically, uninitialized data has been a common source of bugs. Bugs caused by uninitialized data occur regularly in C, for example, because it doesn't have built-in mechanisms to enforce proper initialization of data. C programmers must always remember to initialize data after they allocate it and before they use it. The Java language, by contrast, has built-in mechanisms that help you ensure proper initialization of the memory occupied by a newly-created object. With proper use of these mechanisms, you can prevent an object of your design from ever being created with an invalid initial state.

The Java language has three mechanisms dedicated to ensuring proper initialization of objects: instance initializers (also called instance initialization blocks), instance variable initializers, and constructors. (Instance initializers and instance variable initializers collectively are called "initializers.") All three mechanisms result in Java code that is executed automatically when an object is created. When you allocate memory for a new object with the new operator or the newInstance() method of class Class, the Java virtual machine will insure that initialization code is run before you can use the newly-allocated memory. If you design your classes such that initializers and constructors always produce a valid state for newly-created objects, there will be no way for anyone to create and use an object that isn't properly initialized.

Default initial values

If you provide no explicit initialization to instance variables, they will be awarded predictable default initial values, which are based only on the type of the variable. Table 1 shows the default initial values for each of the variable types. (These are the default initial values for both instance and class variables. Local variables are not given default initial values. They must be initialized explicitly before they are used.)

TypeDefault Value
booleanfalse
byte(byte) 0
short(short) 0
int0
long0L
char\u0000
float0.0f
double0.0d
object referencenull

Table 1. Default values for fields

If you don't explicitly initialize an instance variable, that variable will retain its default initial value when new returns its object reference. For example, here is a class, named CoffeeCup, whose innerCoffee field is not explicitly initialized (there are no constructors or initializers in the class):

// In source packet in file init/ex1/CoffeeCup.java
// This class has no constructors or initializers
class CoffeeCup {
    private int innerCoffee;
    //...
}

As a result, when the reference to a new CoffeeCup object is first returned by new, the innerCoffee field will be its default initial value. Because innerCoffee is an int, its default initial value is zero.

Note that this means that if you explicitly initialize innerCoffee, say to a value of 100, then when each CoffeeCup object is created, innerCoffee will, in effect, be initialized twice. First, innerCoffee will be given its default initial value of zero. Later, the zero will be overwritten with the proper initial value of 100. All of this takes place while the Java virtual machine is creating the new object -- before it returns the reference to the new object. By the time the reference to a new CoffeeCup object is returned from the new operator, the innerCoffee field will be set to 100.

Constructors

The central player in object initialization is the constructor. In Java, constructors are similar to methods, but they are not methods. Like a method, a constructor has a set of parameters and a body of code. Unlike methods, however, constructors have no return type. Like methods, you can give access specifiers to constructors, but unlike methods, constructors with public, protected, or package access are not inherited by subclasses. (Also, instead of determining the ability to invoke a method, the access level of a constructor determines the ability to instantiate an object.)

Constructor basics

In the source file, a constructor looks like a method declaration in which the method has the same name as the class but has no return type. For example, here is a constructor declaration for class CoffeeCup:

// In source packet in file init/ex2/CoffeeCup.java
class CoffeeCup {
    // Constructor looks like a method declaration
    // minus the return type
    public CoffeeCup() {
        // Body of constructor
    }
    // ...
}

As with methods, you can overload constructors by varying the number, types, and order of parameters. Here is a class CoffeeCup with two constructors:

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

When you instantiate an object with new, you must specify a constructor. For example, given the CoffeeCup class above that has two constructors, you could instantiate it in either of these two ways:

// In source packet in file init/ex3/Example3.java
class Example3 {
    public static void main(String[] args) {
        // Create an empty cup
        CoffeeCup cup1 = new CoffeeCup();
        // Create a cup with 355 ml of coffee in it
        CoffeeCup cup2 = new CoffeeCup(355);
    }
}

The no-arg constructor

In Java jargon, constructors that take no parameters (or no arguments) are called "no-arg constructors." In the code shown above, the first instantiation of a CoffeeCup object specifies the no-arg constructor. The second instantiation specifies the constructor that requires an int as its only parameter.

The this() invocation

From within a constructor, you can explicitly invoke another constructor from the same class by using the this() statement. You may want to do this if you have several overloaded constructors in a class, all of which must execute much of the same code. Here's an example:

// 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;
    }
    // ...
}

In this example, the no-arg constructor invokes the other constructor that takes an int as its only parameter. It passes 237 to the other constructor, which assigns that value to innerCoffee.

You cannot call this() from methods, only from constructors. If you do call this()in a constructor, you must call it first, before any other code in the constructor, and you can only call it once. Any code you include after the call to this() will be executed after the invoked constructor completes.

Constructors are not methods

To further illustrate the difference between methods and constructors, consider this fact: The name of a class is a valid name for its methods. In other words, class CoffeeCup could have methods named CoffeeCup:

// In source packet in file init/ex5/CoffeeCup.java
// THIS WORKS, BUT IT IS AN EXAMPLE OF POOR METHOD NAMING
class CoffeeCup {
    private int innerCoffee;
    public CoffeeCup() {    // The constructor
        innerCoffee = 237;
    }
    public void CoffeeCup() {   // The method
        innerCoffee = 99;
    }
    // ...
}

Given the above definition of class CoffeeCup, you could legally do the following:

// In source packet in file init/ex5/Example5.java
class Example5 {
    public static void main(String[] args) {
        CoffeeCup cup = new CoffeeCup(); // invoke the constructor
        cup.CoffeeCup(); // invoke the method
    }
}

Although it is legal to give a method the same name as a class, in practice you should never do so, in part because other programmers might confuse it with a constructor, but also because it breaks many of the rules for good method design. First, a class name is not a verb; it's a noun (at least it should be a noun). Method names should be verbs. You should name methods after the action they perform, and "CoffeeCup" is not an action. Also, "CoffeeCup" doesn't follow the naming convention for methods, in which the first letter is lowercase. The purpose of this example is merely to highlight the fact that constructors aren't methods by showing that a constructor does not conflict with a method that has the same signature.

Default constructors

If you declare a class with no constructors, the compiler will automatically create a default constructor for the class. A default constructor takes no parameters (it's a no-arg constructor) and has an empty body. Because the compiler will automatically generate a default constructor if you don't declare any constructors explicitly, all classes are guaranteed to have at least one constructor. For example, if you declare a CoffeeCup class without declaring a constructor explicitly:

// In source packet in file init/ex6/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee;
    public void add(int amount) {
        innerCoffee += amount;
    }
    //...
}

The compiler will generate the same class file as if you had explicitly declared a no-arg constructor with an empty body:

// In source packet in file init/ex7/CoffeeCup.java
class CoffeeCup {
    private int innerCoffee;
        public CoffeeCup() {
    }
    public void add(int amount) {
        innerCoffee += amount;
    }
    //...
}

The compiler gives default constructors the same access level as their class. In the example above, class CoffeeCup is public, so the default constructor is public. If CoffeeCup had been given package access, the default constructor would be given package access as well.

Instance initialization methods

When you compile a class, the Java compiler creates an instance initialization method for each constructor you declare in the source code of the class. Although the constructor is not a method, the instance initialization method is. It has a name, <init>, a return type, void, and a set of parameters that match the parameters of the constructor from which it was generated. For example, given the following two constructors in the source file for class CoffeeCup:

// In source packet in file init/ex8/CoffeeCup.java
class CoffeeCup {
    public CoffeeCup() {
        //...
    }
    public CoffeeCup(int amount) {
        //...
    }
    // ...
}

the compiler would generate the following two instance initialization methods in the class file for class CoffeeCup, one for each constructor in the source file:

// In binary form in file init/ex8/CoffeeCup.class:
public void <init>(CoffeeCup this) {...}
public void <init>(CoffeeCup this, int amount) {...}

Note that <init> is not a valid Java method name, so you could not define a method in your source file that accidentally conflicted with an instance initialization method. (<init> is not a method in the Java language sense of the term, because it has an illegal name. In the compiled, binary Java class file, however, it is a legal method.)

Also, the this reference passed as the first parameter to <init> is inserted by the Java compiler into the parameter list of every instance method. For example, the method void add(int amount) in the source file for class CoffeeCup would become the void add(CoffeeCup this, int amount) method in the class file. The hidden this reference is the way in which instance methods, including instance initialization methods, are able to access instance data.

If you don't explicitly declare a constructor in a class, the Java compiler will create a default constructor on the fly, then translate that default constructor into a corresponding instance initialization method. Thus, every class will have at least one instance initialization method.

When the compiler generates an instance initialization method, it bases it on a constructor. It gives the method the same parameter list as the constructor, and it puts the code contained in the constructor's body into the method's body. But the instance initialization method does not necessarily represent a mere compilation of the constructor with the name changed to <init> and a return value of void added. Often, the code of an instance initialization method does more than the code defined in the body of its corresponding constructor. The compiler also potentially adds code for any initializers and an invocation of the superclass's constructor.

1 2 3 4 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more