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.

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