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 to3.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
andClassInitializationDemo6
. 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 beforeClassInitializationDemo6
's <clinit>
method, examine the followingClassInitializationDemo6
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 inObjectInitializationDemo2
), 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 disassembleObjectInitializationDemo2
'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 keywordthis
represents in source code. Theinvokespecial
andputfield
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 theObject
superclass. (Remember keywordsuper
'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 (afteraload_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 (viathis
) or a constructor in a superclass (viasuper
), the compiler generates byte code instructions that are the equivalent of placingsuper ();
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); } }