Page 2 of 4

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. 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);
   }
}
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐