<init>
) methods, initialization and inheritance, object images on the heap, and the order in which an object's variables get initialized.
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.)
Type | Default Value |
---|---|
boolean |
false |
byte |
(byte) 0 |
short |
(short) 0 |
int |
0 |
long |
0L |
char |
\u0000 |
float |
0.0f |
double |
0.0d |
object reference |
null |
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.
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 cafe 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:
Object
's fields (this will be quick, because there are none)Liquid
's fields (mlVolume
and temperature
)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.
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:
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, Liquid
s <init>
method will initialize mlVolume
and temperature
to their proper starting values and return. When Liquid
s <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
The strict ordering of instance variable initialization enforced by the Java compiler is, in part, an effort to ensure that during the initialization process, instance variables are never used before they have been initialized to their proper initial values. As illustrated earlier in this article, however, the rules of ordering are not bulletproof. There are ways you can use an instance variable during initialization before it has been initialized to its proper value, while it still has its default value. In the case of instance variable initializers, you can invoke a method that uses a variable declared textually after the variable being initialized. Another way to use an instance variable before it has been properly initialized is to invoke a method from a superclass initializer or constructor that uses instance variables in a subclass.
Unlike C++, which treats the invocation of virtual functions from constructors specially, Java methods invoked from <init>
methods behave the same as if they were invoked from any method. If <init>
in a superclass invokes a method that has been overridden in a subclass, the subclass's implementation of that method will run. If the subclass's method implementation uses instance variables explicitly declared in the subclass, those variables will still have their default initial values.
You should be careful when you invoke methods from initializers or constructors, because you can end up using instance variables before they've been properly initialized -- while they still have their default initial values. It is fine to use variables while they still have their default initial values, so long as it is the result you are aiming for. If you invoke non-private methods from initializers and constructors, remember that later some other programmer could come along, extend your class, and override those methods, thereby thwarting your grand initialization scheme.
Conclusion
Java goes to great lengths to help you give newly-created objects a good start in life. Java's initialization mechanisms help you to ensure that objects you design begin their lives in a valid, predictable state. But these mechanisms do not force you to design objects in this way. In the end, if you want your programs to produce objects that always begin their lives in a proper state, you must use the initialization mechanisms correctly.
For advice on how to put the initialization mechanisms described in this article to use in your programs and designs, see this month's Design Techniques column, "Designing object initialization."
This article was first published under the name Object Initialization in Java in JavaWorld, a division of Web Publishing, Inc., February 1998.
Have an opinion? Be the first to post a comment about this article.
Bill Venners has been writing software professionally for 12 years. Based in Silicon Valley, he provides software consulting and training services under the name Artima Software Company. Over the years he has developed software for the consumer electronics, education, semiconductor, and life insurance industries. He has programmed in many languages on many platforms: assembly language on various microprocessors, C on Unix, C++ on Windows, Java on the Web. He is author of the book: Inside the Java Virtual Machine, published by McGraw-Hill.
Artima provides consulting and training services to help you make the most of Scala, reactive
and functional programming, enterprise systems, big data, and testing.