Download Class and object initialization

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Class and object initialization
Learn how to prepare classes and objects for use in an
executing program
Summary
Jeff celebrates Java 101's first anniversary by exploring class and
object initialization. During this exploration, Jeff introduces you to the
strange concepts of the <clinit> and <init> methods. For answers
to last month's Java 101 Challenge, for this month's homework, and
for additional material related to this article, visit this article's
associated study guide. (4,100 words; November 2, 2001)
By Jeff Friesen
This article marks a milestone in our exploration of client-side Java. We now begin
moving away from simpler Java language concepts toward more advanced theories - and toward a grand tour of Java's standard class library. To begin our journey, this
article focuses on 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 classoriented 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 developerspecified 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 floatingpoint 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
1
4
5
8
iconst_3
putstatic ClassInitializationDemo3/first I
iconst_1
getstatic ClassInitializationDemo3/first I
iadd
// first = 3;
9
putstatic ClassInitializationDemo3/second I
first;
12
return
// second = 1 +
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 stackbased 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 topdown 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
System.out.println
System.out.println
System.out.println
System.out.println
System.out.println
("oid1.d = " + oid1.d);
("oid1.f = " + oid1.f);
("oid1.i = " + oid1.i);
("oid1.l = " + oid1.l);
("oid1.s = " + oid1.s);
("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
System.out.println
System.out.println
System.out.println
System.out.println
System.out.println
System.out.println
System.out.println
System.out.println
("oid2.b = " + oid2.b);
("oid2.by = " + oid2.by);
("oid2.c = " + oid2.c);
("oid2.d = " + oid2.d);
("oid2.f = " + oid2.f);
("oid2.i = " + oid2.i);
("oid2.l = " + oid2.l);
("oid2.s = " + oid2.s);
("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/()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
a = 3
Executing
x = 2
Executing
a = 3
Executing
superclass constructor
subclass constructor
superclass constructor
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 noargument <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 developerspecified 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.
About the author
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.
Resources














Download this article's source code and resource files:
http://www.javaworld.com/javaworld/jw-11-2001/java101/jw-1102java101.zip
For a glossary specific to this article, homework, answers to the Java 101
Challenge, and more, see the Java 101 study guide that accompanies this
article:
http://www.javaworld.com/javaworld/jw-11-2001/jw-1102java101guide.html
The Java Language Specification, Second Edition, James Gosling, Bill Joy, Guy
Steele, Gilad Bracha (Sun Microsystems, 2000):
http://www.javasoft.com/docs/books/jls/second_edition/html/j.title.doc.html
The Java Tutorial:
http://www.javasoft.com/tutorial/
Sun's official Java language definition:
http://java.sun.com/docs/overviews/java/java-overview-1.html
Learn how to ensure that your objects are properly initialized at all times in
"Designing Object Initialization," Bill Venners (JavaWorld, March 1998):
http://www.javaworld.com/jw-03-1998/jw-03-techniques.html
Review Jeff's previous column, "Object-Oriented Language Basics, Part 7"
(JavaWorld, October 2001):
http://www.javaworld.com/javaworld/jw-10-2001/jw-1005-java101.html
Go on to Jeff's next Java 101 lesson: "Trash Talk, Part 1" (JavaWorld,
December 2001):
http://www.javaworld.com/javaworld/jw-12-2001/jw-1207-java101.html
Check out past Java 101 articles:
http://www.javaworld.com/javaworld/topicalindex/jw-ti-java101.html
For more articles on Core Java concepts, browse our Topical Index:
http://www.javaworld.com/channel_content/jw-core-index.shtml
Need some Java help? Visit our Java Beginner discussion:
http://forums.idg.net/webx?50@@.ee6b804
Java experts answer your toughest Java questions in JavaWorld's Java Q&A
column:
http://www.javaworld.com/javaworld/javaqa/javaqa-index.html
For Tips 'N Tricks see:
http://www.javaworld.com/javaworld/javatips/jw-javatips.index.html
Sign up for JavaWorld's free weekly Core Java newsletter:
http://www.idg.net/jw-subscribe

You'll find a wealth of IT-related articles from our sister publications at
IDG.net