Java 101: Class and object initialization

Learn how to prepare classes and objects for use in an executing program

This article marks a milestone in Java 101's exploration of client-side Java, as well as the one year anniversary of the series. Jeff begins moving away from simpler Java language concepts toward more advanced theories -- and toward a grand tour of Java's standard class library. Begin the journey this month by learning all about Java initialization.

Initialization prepares classes and objects for use during a program's execution. Although we tend to think of initialization in terms of assigning values to variables, initialization is so much more. For example, initialization might involve opening a file and reading its contents into a memory buffer, registering a database driver, preparing a memory buffer to hold an image's contents, acquiring the resources necessary for playing a video, and so on. Think of anything that prepares a class or an object for use in a program as initialization.

Java supports initialization via language features collectively known as initializers. Because Java handles class initialization differently from object initialization, and because classes initialize before objects, we first explore class initialization and class-oriented initializers. Later, we explore object initialization and object-oriented initializers.

Class initialization

A program consists of classes. Before a Java application runs, Java's class loader loads its starting class -- the class with a public static void main(String [] args) method -- and Java's byte code verifier verifies the class. Then that class initializes. The simplest kind of class initialization is automatic initialization of class fields to default values. Listing 1 demonstrates that initialization:

Listing 1. ClassInitializationDemo1.java

// ClassInitializationDemo1.java
class ClassInitializationDemo1
{
   static boolean b;
   static byte by;
   static char c;
   static double d;
   static float f;
   static int i;
   static long l;
   static short s;
   static String st;
   public static void main (String [] args)
   {
      System.out.println ("b = " + b);
      System.out.println ("by = " + by);
      System.out.println ("c = " + c);
      System.out.println ("d = " + d);
      System.out.println ("f = " + f);
      System.out.println ("i = " + i);
      System.out.println ("l = " + l);
      System.out.println ("s = " + s);
      System.out.println ("st = " + st);
   }
}

ClassInitializationDemo1's static keyword introduces a variety of class fields. As you can see, no explicit values assign to any of those fields. And yet, when you run ClassInitializationDemo1, you see the following output:

b = false
by = 0
c =  
d = 0.0
f = 0.0
i = 0
l = 0
s = 0
st = null

The false, 0, 0.0, and null values are the type-oriented representations of default values. They represent the result of all bits automatically set to zero in each class field. And what automatically set those bits to zero? The JVM, after a class is verified. (Note: In the preceding output, you do not see a value beside c = because the JVM interprets c's default value as the nondisplayable null value.)

Class field initializers

After automatic initialization, the next simplest kind of class initialization is the explicit initialization of class fields to values. Each class field explicitly initializes to a value via a class field initializer. Listing 2 illustrates several class field initializers:

Listing 2. ClassInitializationDemo2.java

// ClassInitializationDemo2.java
class ClassInitializationDemo2
{
   static boolean b = true;
   static byte by = 1;
   static char c = 'A';
   static double d = 1.2;
   static float f = 3.4f;
   static int i = 2;
   static long l = 3;
   static short s = 4;
   static String st = "abc";
   public static void main (String [] args)
   {
      System.out.println ("b = " + b);
      System.out.println ("by = " + by);
      System.out.println ("c = " + c);
      System.out.println ("d = " + d);
      System.out.println ("f = " + f);
      System.out.println ("i = " + i);
      System.out.println ("l = " + l);
      System.out.println ("s = " + s);
      System.out.println ("st = " + st);
   }
}

In contrast to ClassInitializationDemo1, in ClassInitializationDemo2 a class field initializer explicitly assigns a nondefault value to each class field. Simply put, a class field initializer consists of the assignment operator (=) and an expression that the JVM evaluates after a class loads and before any of that class's developer-specified methods execute. The assignment operator assigns the expression's value to the associated class field. When run, ClassInitializationDemo2 produces the following output:

b = true
by = 1
c = A
d = 1.2
f = 3.4
i = 2
l = 3
s = 4
st = abc

Although the output is as expected, what executes the class field initializers that explicitly initialize ClassInitializationDemo2's class fields? The answer: After the JVM zeroes the bits in all class fields, it calls a special JVM-level method to execute the byte code instructions that comprise the class's class field initializers. That method is known as <clinit>.

When you compile a class containing at least one class field initializer, the compiler generates code for <clinit>. After the JVM's class loader loads the class, after the byte code verifier verifies the class's byte codes, and after the JVM allocates memory for the class fields and zeroes all bits in those class fields, the JVM calls the class's <clinit> method (if present). The <clinit> method's byte code instructions execute all class field initializers. To see what those byte code instructions look like for the above ClassInitializationDemo2 class, check out Listing 3:

Listing 3. ClassInitializationDemo2's <clinit> method

 0   iconst_1
 1   putstatic ClassInitializationDemo2/b Z          // boolean b = true;
 4   iconst_1
 5   putstatic ClassInitializationDemo2/by B         // byte by = 1;
 8   bipush 65
10   putstatic ClassInitializationDemo2/c C          // char c = 'A';
13   ldc2_w #1.200000
16   putstatic ClassInitializationDemo2/d D          // double d = 1.2;
19   ldc #3.400000
21   putstatic ClassInitializationDemo2/f F          // float f = 3.4f; 
24   iconst_2
25   putstatic ClassInitializationDemo2/i I          // int i = 2;
28   ldc2_w #3
31   putstatic ClassInitializationDemo2/l J          // long l = 3; 
34   iconst_4
35   putstatic ClassInitializationDemo2/s S          // short s = 4;
38   ldc "abc"
40   putstatic ClassInitializationDemo2/st Ljava/lang/String;  // String st = "abc";
43   return

Listing 3 presents some insight into how ClassInitializationDemo2's <clinit> method works. Each line presents a number and a byte code instruction. The number represents the instruction's zero-based address and is not important to this discussion. The first instruction, iconst_1, pushes integer constant 1 onto a stack, and the second instruction, putstatic ClassInitializationDemo2/b Z, pops that constant from the stack and assigns it to boolean class field b. (At the JVM level, at least with Sun's JVM, the Boolean true value is represented as integer constant 1.) The Z appearing to the right of the putstatic instruction identifies the type of b as Boolean. Similarly, B identifies the byte type, C identifies the character type, D identifies the double-precision floating-point type, F identifies the floating-point type, J identifies the long integer type, and S identifies the short integer type. The bipush, ldc2_w, ldc, and other iconst instructions push other constants onto the stack, and their respective putstatic instructions pop those values from the stack before assigning them to various class fields. The final return instruction causes execution to leave the <clinit> method. At that point, the main() method starts to execute.

Some programs require class fields to refer to previously declared class fields. Java supports that activity by letting you specify the name of a previously declared class field in the expression portion of a subsequently declared class field's class field initializer, as Listing 4 demonstrates:

Listing 4. ClassInitializationDemo3.java

// ClassInitializationDemo3.java
class ClassInitializationDemo3
{
   static int first = 3;
   static int second = 1 + first;
   public static void main (String [] args)
   {
      System.out.println ("first = " + first);
      System.out.println ("second = " + second);
   }
}

ClassInitializationDemo3 declares class field first and explicitly assigns 3 to that field. Then, ClassInitializationDemo3 declares class field second and refers to first in second's class initializer expression. When you run the program, you see the following output:

first = 3
second = 4

If you examine the byte code instructions for ClassInitializationDemo3's <clinit> method, you see something similar to Listing 5:

Listing 5. ClassInitializationDemo3's <clinit> method

 0   iconst_3
 1   putstatic ClassInitializationDemo3/first I   // first = 3;
 4   iconst_1
 5   getstatic ClassInitializationDemo3/first I
 8   iadd
 9   putstatic ClassInitializationDemo3/second I  // second = 1 + first;
12   return

Listing 5 shows byte code instructions that assign 3 to first and add constant 1 to first's contents. The result of that addition ends up in a temporary stack-based variable. The instructions subsequently assign the contents of the temporary stack-based variable to second.

Although a subsequently declared class field can refer to a previously declared class field, the reverse is not true: You cannot declare a class field initializer that refers to a class field declared later in source code. In other words, Java does not permit forward references with class field initializers, as the following code fragment demonstrates:

static int second = 1 + first;
static int first = 3;

When the compiler encounters either the code above or another forward reference code fragment during compilation, it generates an error message because the developer's intention is unclear. Should the compiler regard first as containing 0, which results in second initializing to 1, or should the compiler regard first as containing 3, which results in second initializing to 4? To prevent that confusion, Java prohibits class field initializers from making forward references to other class fields.

Class block initializers

Although sufficient for class field initialization, class field initializers prove inadequate for more complex class initialization. For example, suppose you need to read a file's contents into a buffer before the main() method executes. What do you do? Java meets that challenge by providing the class block initializer. A class block initializer consists of keyword static followed by an open brace character ({), initialization code, and a close brace character (}). Furthermore, a class block initializer appears within a class, but not within any of that class's methods, as Listing 6 demonstrates:

Listing 6. ClassInitializationDemo4.java

// ClassInitializationDemo4.java
import java.io.*;
class ClassInitializationDemo4
{
   static String [] filenames;
   static
   {
      System.out.println ("Acquiring filenames");
      filenames = new File (".").list ();
      System.out.println ("Filenames acquired");
   }
   public static void main (String [] args)
   {
      System.out.println ("Displaying filenames\n");
      for (int i = 0; i < filenames.length; i++)
           System.out.println (filenames [i]);
   }
}

ClassInitializationDemo4 declares a filenames array field variable and then introduces a class block initializer. That initializer displays a status message, obtains a list of all filenames for those files that appear in the current directory, and displays a second status message. All that activity takes place before the main() method executes. When main() executes, a status message displays, along with a list of filenames. The result resembles the following output:

Acquiring filenames
Filenames acquired
Displaying filenames
ClassInitializationDemo4.java
ClassInitializationDemo4.class

In addition to placing the class field initializers' byte code instructions in a class's <clinit> method, the compiler places the byte code instructions of each encountered class block initializer in that same method. The compiler places these instructions in the <clinit> method according to a specific order. Because the compiler compiles a class in a top-to-bottom fashion, it places in the <clinit> method, in a top-to-bottom order, the equivalent byte code instructions to each encountered class field initializer and class block initializer. Refer back to Listing 6: The compiler places the class block initializer's byte code instructions in ClassInitializationDemo4's <clinit> method. If that class specified a class field initializer for its filenames class field, the byte code instructions comprising that class field initializer would appear before the class block initializer's byte code instructions in the <clinit> method.

After studying ClassInitializationDemo4's source code, you might wonder how useful class block initializers are. After all, you could easily move all code from ClassInitializationDemo4's class block initializer to its main() method. Nevertheless, class block initializers are useful. For example, Sun's JDBC (Java Database Connectivity) API uses class block initializers to simplify database driver registration. Consider the following code fragment:

Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");      

The code fragment calls Class's forName() method to load the JdbcOdbcDriver class (located in the sun.jdbc.odbc package). Once that code fragment completes, the class loads, and the database driver associated with the JdbcOdbcDriver class registers with JDBC. What causes that registration to occur? The answer is Java statements that comprise JdbcOdbcDriver's class block initializer. (I have more to say about JDBC in a future article.)

When working with class block initializers, keep in mind two more items: First, any variable that you declare in a class block initializer is local to that block. No code outside the block can access the variable. Second, Java permits you to declare a constant class field without a class field initializer as long as you explicitly initialize that constant in a class block initializer. Furthermore, within the class block initializer, you must initialize the constant before you attempt to read its value. Listing 7 illustrates both points:

Listing 7. ClassInitializationDemo5.java

// ClassInitializationDemo5.java
import java.io.*;
class ClassInitializationDemo5
{
   final static double PI;
   static
   {
      PI = 3.14159;
      int i;
      for (i = 0; i < 5; i++)
           System.out.println (i);
   }
   static int j = i;
   public static void main (String [] args)
   {
      System.out.println ("PI = " + PI);
   }
}

When you compile ClassInitializationDemo5, the compiler reports an error when it encounters static int j = i; because it cannot find i -- i is local to the class block initializer. However, if you comment out static int j = i; and recompile, you don't receive a compiler error. Instead, you receive the following output:

0
1
2
3
4
PI = 3.14159

You might think it bizarre to see the declaration of constant PI without a class field initializer to initialize that constant. However, as long as PI explicitly initializes to 3.14159 in either a class field initializer or in a class block initializer, the compiler does not complain.

Class initialization and class hierarchies

Thus far, you have only seen class field initializers and class block initializers in the context of a single class. How does class initialization work in the context of a class hierarchy? When a class hierarchy is involved, the compiler creates a separate <clinit> method for each class in that hierarchy. At runtime, the JVM loads all hierarchy classes and calls their <clinit> methods in a top-to-bottom order. That means the highest superclass's <clinit> method (which is Object's <clinit> method) executes first. After Object's <clinit> method completes, the next highest superclass's <clinit> method executes. The process continues in a top-down fashion until the class with the main() method's <clinit> method (if present) executes. Listing 8 demonstrates the <clinit> execution order:

Listing 8. ClassInitializationDemo6.java

// ClassInitializationDemo6.java
class Parent
{
   static int a = 1;
   static
   {
      System.out.println ("a = " + a);
      System.out.println ("Parent initializer");
   }
}
class ClassInitializationDemo6 extends Parent
{
   static int b = 2 + a;
   static
   {
      System.out.println ("b = " + b);
      System.out.println ("Child initializer");
      System.out.println ("a = " + a);
   }
   public static void main (String [] args)
   {
   }
}

ClassInitializationDemo6 introduces a pair of classes: Parent and ClassInitializationDemo6. Each class's <clinit> method executes the byte code instructions comprising that class's class field initializer and class block initializer. To prove to yourself that Parent's <clinit> method executes before ClassInitializationDemo6's <clinit> method, examine the following ClassInitializationDemo6 output:

a = 1
Parent initializer
b = 3
Child initializer
a = 1

The output shows that Parent's class field initializer = 1; executes first. Next, Parent's class block initializer executes. Moving on, ClassInitializationDemo6's class field initializer = 2 + a; executes. Finally, ClassInitializationDemo6's class block initializer executes. And that is pretty much all there is to know regarding class initialization and class hierarchies.

Object initialization

Now that you have seen class initialization at work, it is time to focus on object initialization. As you will discover, the initializers that perform object initialization mirror those initializers that perform class initialization. As with class initialization, the simplest kind of object initialization is automatic initialization of object fields to default values. Listing 9 illustrates that type of initialization:

Listing 9. ObjectInitializationDemo1.java

// ObjectInitializationDemo1.java
class ObjectInitializationDemo1
{
   boolean b;
   byte by;
   char c;
   double d;
   float f;
   int i;
   long l;
   short s;
   String st;
   public static void main (String [] args)
   {
      ObjectInitializationDemo1 oid1 = new ObjectInitializationDemo1 ();
      System.out.println ("oid1.b = " + oid1.b);
      System.out.println ("oid1.by = " + oid1.by);
      System.out.println ("oid1.c = " + oid1.c);
      System.out.println ("oid1.d = " + oid1.d);
      System.out.println ("oid1.f = " + oid1.f);
      System.out.println ("oid1.i = " + oid1.i);
      System.out.println ("oid1.l = " + oid1.l);
      System.out.println ("oid1.s = " + oid1.s);
      System.out.println ("oid1.st = " + oid1.st);
   }
}

ObjectInitializationDemo1 mirrors ClassInitializationDemo1 in that it introduces a variety of fields -- object fields, to be exact. Furthermore, no explicit values assign to any of those fields.

You see the following output when ObjectInitializationDemo1 runs:

b = false
by = 0
c =  
d = 0.0
f = 0.0
i = 0
l = 0
s = 0
st = null

This time, the JVM zeroes all object fields' bits. Unlike class fields, which the JVM zeroes after a class loads and is verified, the JVM only zeroes the bits of a class's object fields when a program creates an object from that class. That activity should come as no surprise when you consider that object fields bind to objects. Therefore, object fields do not exist until a program creates an object. Furthermore, each object receives its own copies of a class's object fields.

Object field initializers

The next simplest kind of object initialization is the explicit initialization of object fields to values. Each object field explicitly initializes to a value via an object field initializer. Listing 10 shows several object field initializers:

Listing 10. ObjectInitializationDemo2.java

// ObjectInitializationDemo2.java
class ObjectInitializationDemo2
{
   boolean b = true;
   byte by = 1;
   char c = 'A';
   double d = 1.2;
   float f = 3.4f;
   int i = 2;
   long l = 3;
   short s = 4;
   String st = "abc";
   public static void main (String [] args)
   {
      ObjectInitializationDemo2 oid2 = new ObjectInitializationDemo2 ();
      System.out.println ("oid2.b = " + oid2.b);
      System.out.println ("oid2.by = " + oid2.by);
      System.out.println ("oid2.c = " + oid2.c);
      System.out.println ("oid2.d = " + oid2.d);
      System.out.println ("oid2.f = " + oid2.f);
      System.out.println ("oid2.i = " + oid2.i);
      System.out.println ("oid2.l = " + oid2.l);
      System.out.println ("oid2.s = " + oid2.s);
      System.out.println ("oid2.st = " + oid2.st);
   }
}

In contrast to ObjectInitializationDemo1, in ObjectInitializationDemo2 an object field initializer explicitly assigns a nondefault value to each object field. Essentially, an object field initializer consists of the assignment operator (=) and an expression that evaluates during object creation. The assignment operator assigns the expression's value to the associated object field. When run, ObjectInitializationDemo2 produces the following output:

oid2.b = true
oid2.by = 1
oid2.c = A
oid2.d = 1.2
oid2.f = 3.4
oid2.i = 2
oid2.l = 3
oid2.s = 4
oid2.st = abc

What is responsible for executing the object field initializers? Would you believe that the constructor is? As strange as it might seem, the compiler inserts byte code instructions into a class's constructor to execute object field initializers. But there's more: When you look at a constructor from the JVM's perspective, you no longer see a constructor. Instead, you see what the JVM refers to as an <init> method.

During class compilation, the compiler generates an <init> method for each of that class's constructors. If that class contains no constructors (as in ObjectInitializationDemo2), the compiler generates an <init> method that matches the default no-argument constructor. It is important to realize that each constructor has its own corresponding <init> method and that the compiler places byte code instructions, apart from the instructions you specify (via Java source code), into that method. Some of those instructions serve to execute object field initializers.

Take a close look at Listing 10. You cannot see its presence, but a constructor does exist. If you could see that constructor, it would probably look like the following code fragment:

ObjectInitializationDemo2 ()
{
}

That's right! The constructor would appear empty. Suppose you disassemble ObjectInitializationDemo2's class file. In that disassembly, you would encounter a no-argument <init> method that matches the default no-argument constructor. And what instructions would you find in that method? Take a look at Listing 11:

Listing 11. ObjectInitializationDemo2's no-argument <init> method

 0        aload_0
 1        invokespecial java/lang/Object/<init>()V
 4        aload_0
 5        iconst_1
 6        putfield ObjectInitializationDemo2/b Z
 9        aload_0
10        iconst_1
11        putfield ObjectInitializationDemo2/by B
14        aload_0
15        bipush 65
17        putfield ObjectInitializationDemo2/c C
20        aload_0
21        ldc2_w #1.200000
24        putfield ObjectInitializationDemo2/d D
27        aload_0
28        ldc #3.400000
30        putfield ObjectInitializationDemo2/f F
33        aload_0
34        iconst_2
35        putfield ObjectInitializationDemo2/i I
38        aload_0
39        ldc2_w #3
42        putfield ObjectInitializationDemo2/l J
45        aload_0
46        iconst_4
47        putfield ObjectInitializationDemo2/s S
50        aload_0
51        ldc "abc"
53        putfield ObjectInitializationDemo2/st Ljava/lang/String;
56        return

Apart from Listing 11's byte code instructions executing ObjectInitializationDemo2's object field initializers, a close examination of Listing 11 reveals some interesting items about how Java works:

  • Note the aload_0 instruction. That instruction pushes an address onto a stack. And what address does that instruction push? The current object address -- as keyword this represents in source code. The invokespecial and putfield instructions pop that address from the stack and use the address to identify the proper object when making a call to an object method or writing to an object field.
  • Note the invokespecial java/lang/Object/<init>()V instruction. That instruction calls the default no-argument constructor -- to be precise, the default no-argument <init> method -- in the Object superclass. (Remember keyword super's use in calling a superclass constructor? You are seeing how Java uses that keyword at the byte code level.)
  • Note the location of invokespecial java/lang/Object/<init>()V. It is no accident that the compiler places that instruction as the second instruction (after aload_0) in the default no-argument <init> method. In accordance with the way Java works, a constructor must first either call another constructor in the same class or a constructor in its superclass. If a constructor does not explicitly call another constructor in the same class (via this) or a constructor in a superclass (via super), the compiler generates byte code instructions that are the equivalent of placing super (); at a constructor's start.

We will explore the second and third items in the above list later in this section, when we examine object initialization and class hierarchies. But first, we need to explore object field initializers and forward references, along with object block initializers.

As with class fields, some programs require object fields to refer to previously declared object fields. Java supports that activity by allowing you to specify the name of a previously declared object field in the expression portion of a subsequently declared object field's initializer. However, just as you cannot use forward references with class field initializers, you cannot use forward references with object field initializers. Listing 12 demonstrates both concepts:

Listing 12. ObjectInitializationDemo3.java

// ObjectInitializationDemo3.java
class ObjectInitializationDemo3
{
//   int forwardReference = first;
   int first = 3;
   int second = 1 + first;
   public static void main (String [] args)
   {
      ObjectInitializationDemo3 oid3 = new ObjectInitializationDemo3 ();
      System.out.println ("oid3.first = " + oid3.first);
      System.out.println ("oid3.second = " + oid3.second);
   }
}

int second = 1 + first; can legally refer to first because first's declaration precedes second's declaration. However, it is illegal for int forwardReference = first; to refer to first -- because that would be a forward reference. To prove to yourself that you cannot make forward references to subsequently declared object fields from a previously declared object field initializer, uncomment the commented-out int forwardReference = first; in ObjectInitializationDemo3 and recompile.

Object block initializers

Object field initializers are sufficient for the initialization of object fields. However, they prove inadequate for more complex object initialization. To parallel class block initializers, Java supports the object block initializer. An object block initializer consists of an open brace character ({), initialization code, and a close brace character (}). Furthermore, an object block initializer appears within a class but not within any of that class's methods, as you can see in Listing 13:

Listing 13. ObjectInitializationDemo4.java

// ObjectInitializationDemo4.java
import java.io.*;
class ObjectInitializationDemo4
{
   {
      System.out.println ("Initializing object " + hashCode ());
      int localVariable = 1;
   }
   ObjectInitializationDemo4 (String msg)
   {
      System.out.println (msg);
//      System.out.println (localVariable);
   }
   public static void main (String [] args)
   {
      ObjectInitializationDemo4 oid41 = new ObjectInitializationDemo4 ("1");
      ObjectInitializationDemo4 oid42 = new ObjectInitializationDemo4 ("2");
   }
}

ObjectInitializationDemo4's main() method creates a pair of ObjectInitializationDemo4 objects. For each object, the object block initializer executes before the constructor. That initializer prints a message, which includes the object's hashcode, and then declares/initializes a local integer variable. (To prove to yourself that you cannot access a local variable from outside the scope of an object block initializer, uncomment the commented-out System.out.println (localVariable); method call and recompile.) Once the object block initializer completes, the constructor executes. The following output shows that object block initializers execute before constructors:

Initializing object 2765838
1
Initializing object 4177328
2

In many situations, you will not use object block initializers because you can use constructors to perform complex initialization tasks. However, certain situations require object block initializers. For example, anonymous inner classes (which I explore in a future article) often require object block initializers to perform complex initialization tasks. Anonymous inner classes require object block initializers because anonymous inner classes have no names, constructors take on the names of their classes, and you cannot declare constructors in classes that have no names.

Object initialization and class hierarchies

How do object field initializers and object block initializers work in the context of a class hierarchy? For some insight, we need to think in terms of <init> methods and refer back to Listing 11. That listing shows us a principle of initialization: If a subclass does not declare a constructor, the compiler produces a corresponding <init> method that explicitly calls its superclass's default no-argument <init> method. Furthermore, for each subclass constructor that explicitly calls a superclass constructor, the compiler inserts a call to the superclass constructor's equivalent <init> method at the start of the subclass's <init> method.

Following the call to a superclass's <init> method, a subclass's <init> method executes byte code instructions for each subclass object field initializer and object block initializer. Those byte code instructions execute the initializers in the same order as they appear in source code. Furthermore, the byte code instructions duplicate in each subclass constructor that calls a superclass constructor. As Listing 14 demonstrates, that duplication is necessary because the developer could specify a call to any of the subclass's constructors, and object field initializers and object block initializers must execute, regardless of the subclass constructor that the developer chooses to call:

Listing 14. ObjectInitializationDemo5.java

// ObjectInitializationDemo5.java
class Parent
{
   int x = 1;
   {
       System.out.println ("x = " + ++x);
   }
   Parent ()
   {
       System.out.println ("Executing superclass constructor");
   }
}
class ObjectInitializationDemo5 extends Parent
{
   int a = 2;
   {
      System.out.println ("a = " + ++ a);
   }
   ObjectInitializationDemo5 ()
   {
      System.out.println ("Executing subclass constructor");
   }
   ObjectInitializationDemo5 (String msg)
   {
      System.out.println (msg);
   }
   public static void main (String [] args)
   {
      ObjectInitializationDemo5 oid51 = new ObjectInitializationDemo5 ();
      ObjectInitializationDemo5 oid52 =
       new ObjectInitializationDemo5 ("Executing other subclass constructor");
   }
}

ObjectInitializationDemo5 declares classes Parent and ObjectInitializationDemo5. Each class declares an object field initializer, an object block initializer, and a constructor. To prove to yourself the previously mentioned <init> method calling order and that the object field/block initializer's byte code instructions duplicate, examine the following output:

x = 2
Executing superclass constructor
a = 3
Executing subclass constructor
x = 2
Executing superclass constructor
a = 3
Executing other subclass constructor

The first four output lines reflect the initialization of the object referenced by oid51, and the last four output lines reflect the initialization of the object referenced by oid52. In what order does that initialization occur? Let's find out. To begin, because ObjectInitializationDemo5 features no class field initializers or class block initializers, no class initialization takes place. Instead, execution begins with the main() method. main() creates an ObjectInitializationDemo5 object whose reference assigns to oid51. Notice the call to the default ObjectInitializationDemo5() constructor.

Before the ObjectInitializationDemo5() constructor -- that is, ObjectInitializationDemo5's no-argument <init> method -- executes System.out.println ("Executing subclass constructor");, the <init> method calls Parent's no-argument <init> method. In turn, Parent's no-argument <init> method calls Object's default no-argument <init> method. Once Object's default no-argument <init> method completes, Parent's <init> method continues by executing byte code instructions that correspond to the = 1 object field initializer. Then, byte code instructions corresponding to System.out.println ("x = " + ++x); in Parent's object block initializer execute. Once that completes, byte code instructions that correspond to the Parent constructor's System.out.println ("Executing superclass constructor"); method call then execute. Then Parent's no-argument <init> method completes, and control returns to ObjectInitializationDemo5's <init> method.

ObjectInitializationDemo5's <init> method continues by executing byte code instructions that correspond to the = 2 object field initializer. Next, byte code instructions corresponding to System.out.println ("a = " + ++ a); in ObjectInitializationDemo5's object block initializer then execute. Finally, byte code instructions that correspond to the ObjectInitializationDemo5() constructor's System.out.println ("Executing subclass constructor"); method call then execute, and ObjectInitializationDemo5's no-argument <init> method completes. Initialization continues with ObjectInitializationDemo5 oid52 = new ObjectInitializationDemo5 ("Executing other subclass constructor");. (I leave tracing that initialization as an exercise for you to complete.)

It might surprise you to realize that a superclass object field/block initializer can access a field in a subclass. However, allowing that behavior is not a good idea because superclass initialization occurs before subclass initialization -- and the subclass fields thus contain only default values. Therefore, superclass initializer access to subclass fields produces incorrect results. For a demonstration, check out Listing 15:

Listing 15. ObjectInitializationDemo6.java

// ObjectInitializationDemo6.java
class Parent
{
   {
      System.out.println ("a = " + ((ObjectInitializationDemo6) this).a);
   }
}
class ObjectInitializationDemo6 extends Parent
{
   int a = 2;
   public static void main (String [] args)
   {
      new ObjectInitializationDemo6 ();
   }
}

When you run ObjectInitializationDemo6, you get a = 0 as output. Clearly, a does not contain 2 because ObjectIntializationDemo6's object initializers have not yet run.

Before we leave our examination of object initialization and class hierarchies, it might interest you to know that there exists a situation in which you can declare a class hierarchy -- in which each class has its own initializers -- and, when you construct an object, no initializer executes. That situation arises when a subclass constructor A calls another constructor B in the same subclass (via this). From a JVM perspective, that implies that a corresponding <init> method A calls <init> method B (in the same subclass). Java assumes that method B will call either an <init> method that corresponds to a superclass constructor or another subclass constructor's <init> method. As a result, <init> method A begins with byte code instructions that call <init> method B. However, following that call, <init> method A does not execute byte code instructions that correspond to object field initializers and object block initializers. Instead, it executes byte code instructions that correspond to developer-specified Java source code. Method A doesn't need to execute those instructions that correspond to initializers because Java expects another constructor/<init> method (which calls a superclass constructor) to perform that task. Without proper care, this scenario can lead to something quite bizarre, as Listing 16 illustrates:

Listing 16. ObjectInitializationDemo7.java

// ObjectInitializationDemo7.java
class Parent
{
   int a = 3;
   {
      System.out.println ("a = " + a);
   }
}
class ObjectInitializationDemo7 extends Parent
{
   int b = 1;
   {
      System.out.println ("b = " + b);
   }
   ObjectInitializationDemo7 ()
   {
      this (1);
   }
   ObjectInitializationDemo7 (int x)
   {
      this ();
   }
   public static void main (String [] args)
   {
      System.out.println (new ObjectInitializationDemo7 ().a);
   }
}

ObjectInitializationDemo7 never executes the object field/block initializers in either the Parent class or in the ObjectInitializationDemo7 class. This is because each constructor compiles to an <init> method whose first byte code instructions recursively call the other <init> method. That situation continues until the JVM reports a stack overflow error. If you happen to observe a disassembly of ObjectInitializationDemo7() and ObjectInitializationDemo7(int x), you will not see any byte code instructions that execute the object field initializer and object block initializer in ObjectInitializationDemo7. All you will see are byte code instructions that call the other <init> method. You can ensure the execution of your classes' object field/block initializers by avoiding such architectural flaws.

Review

This article showed you how classes and objects initialize. You learned about various initializers, including class field initializers, class block initializers, object field initializers, and object block initializers. You also learned about the seemingly strange JVM-level <clinit> and <init> methods, and saw that they are not as strange as you might think. Finally, you looked under each method's hood and observed the actual Java byte code instructions that execute various initializers. And that gave you a deeper understanding of how initialization works in the context of class hierarchies. Hopefully, you can use that knowledge to avoid problems in which superclass initialization code attempts to access subclass fields prior to their initialization, and recursive constructor calls overflow the stack and perform no initialization.

I encourage you to email me with any questions you might have involving either this or any previous article's material. (Please, keep such questions relevant to material discussed in this column's articles.) Your questions and my answers will appear in the relevant study guides.

In next month's article, you will learn about garbage collection.

Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he wrote his own Java book for beginners -- Java 2 By Example (QUE, 2000) -- and helped write a second Java book, Special Edition Using Java 2 Platform (QUE, 2001). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more