Sponsored Link •
|
Advertisement
|
The final step required to ready a class or interface for its first active use is initialization, the process of setting class variables to their proper initial values. As used here, a "proper" initial value is the programmer's desired starting value for a class variable. A proper initial value contrasts with the default initial value given to class variables during preparation. As described above, the virtual machine assigns default values based only on each variable's type. Proper initial values, by contrast, are based on some master plan known only to the programmer.
In Java code, a proper initial value is specified via a class variable initializer or static initializer. A class variable initializer is an equals sign and expression next to a class variable declaration, as in:
// On CD-ROM in file classlife/ex1/Example1a.java class Example1a { // "= 3 * (int) (Math.random() * 5.0)" is the class variable // initializer static int size = 3 * (int) (Math.random() * 5.0); }
A static initializer is a block of code introduced by the static
keyword, as in:
// On CD-ROM in file classlife/ex1/Example1b.java class Example1b { static int size; // This is the static initializer static { size = 3 * (int) (Math.random() * 5.0); } }
All the class variable initializers and static initializers of a type are collected by the Java compiler and
placed into one special method. For classes, this method is called the class initialization
method; for interfaces, the interface initialization method. In Java class files for both
classes and interfaces, this method is named "
". Regular methods of a Java
application cannot invoke a
method. This kind of method can only be
invoked by the Java virtual machine, which invokes it to set a type's static variables to their proper initial
values.
Initialization of a class consists of two steps:
Object
, then all the classes on down
the inheritance hierarchy to the class being actively used. Superclasses will be initialized before subclasses.
Initialization of an interface does not require initialization of its superinterfaces. Initialization of an interface consists of only one step:
The code of a
method does not explicitly invoke a superclass's
method. Before a Java virtual machine invokes the
method of a class, therefore, it must make certain the
methods of superclasses have been executed.
Java virtual machines must also make sure the initialization process is properly synchronized. If multiple threads need to initialize a class, only one thread should be allowed to perform the initialization while the other threads wait. After the active thread completes the initialization process, it must notify any waiting threads. See Chapter 20, "Thread Synchronization," for information about synchronization, wait and notify.
()
MethodAs mentioned above, Java compilers place the code for class variable initializers and static initializers
into the
method of the class file in the order in which they appear in the
class or interface declaration. For example, given this class:
// On CD-ROM in file classlife/ex1/Example1c.java class Example1c { static int width; static int height = (int) (Math.random() * 2.0); // This is the static initializer static { width = 3 * (int) (Math.random() * 5.0); } }The Java compiler generates the following
()
method:
// The code for height's class variable initializer begins here // Invoke Math.random(), which will push // a double return value 0 invokestatic #6 <Method double random()> 3 ldc2_w #8 <Double 2.0> // Push double constant 2.0 6 dmul // Pop two doubles, multiply, push result 7 d2i // Pop double, convert to int, push int // Pop int, store into class variable // height 8 putstatic #5 <Field int height> // The code for the static initializer begins here 11 iconst_3 // Push int constant 3 // Invoke Math.random(), which will push // a double return value 12 invokestatic #6 <Method double random()> 15 ldc2_w #10 <Double 5.0> // Push double constant 5.0 18 dmul // Pop two doubles, multiply, push result 19 d2i // Pop double, convert to int, push int 20 imul // Pop two ints, multiply, push int result // Pop int, store into class variable // width 21 putstatic #7 <Field int width> 24 return // Return void from <clinit> methodThis
()
method first executes the code for
Example1c
's only class variable initializer, which initializes
height
, then executes the code for the static initializer, which initializes
width
. The initialization is done in this order because the class variable initializer
appears textually before the static initializer in the source code of the Example1c
class.
Not all classes will necessarily have a
method in their class file. If a
class declares no class variables or static initializers, it won't have a
method. If a class declares class variables, but doesn't explicitly initialize them with class variable initializers
or static initializers, it won't have a
method. If a class contains only class
variable initializers for static final variables, and those class variable initializers use compile-time constant
expressions, that class won't have a
method. Only those classes that
actually require Java code to be executed to initialize class variables to proper initial values will have a class
initialization method.
Here's an example of a class that won't be awarded a
method by the
Java compiler:
// On CD-ROM in file classlife/ex1/Example1d.java class Example1d { static final int angle = 35; static final int length = angle * 2; }Class
Example1d
declares two constants, angle
and
length
, and initializes them with expressions that are compile-time constants. The
compiler knows that angle
represents the value 35 and length
represents the value 70. When the Example1d
class is loaded by a Java virtual
machine, angle
and length
are not stored as class variables in
the method area. As a result, no ()
method is needed to initialize them. The
angle
and length
fields are not class variables, they are
constants, which are treated specially by the Java compiler.
Instead of treating Example1d
's angle
and
length
fields as class variables, the Java compiler places the constant
int
values they represent into the constant pool or bytecode streams of any class that
uses them. For example, if a class uses Example1d
's angle
field,
that class will not have in its constant pool a symbolic reference to the angle
field of
class Example1d
. Instead, the class will have operands embedded in its bytecode
streams that have the value 35. If the constant value of angle
were outside the range
of a short
(-32,768 to 32,767), say 35,000, the class would have a
CONSTANT_Integer_info
entry in its constant pool with the value of 35,000.
Here's a class that uses both a constant and a class variable from other classes:
// On CD-ROM in file classlife/ex1/Example1e.java class Example1e { // The class variable initializer for symbolicRef uses a symbolic // reference to the size class variable of class Example1a static int symbolicRef = Example1a.size; // The class variable initializer for localConst doesn't use a // symbolic reference to the length field of class Example1d. // Instead, it just uses a copy of the constant value 70. static int localConst = Example1d.length * (int) (Math.random() * 3.0); }
The Java compiler generates the following
method for class
Example1e:
// The code for symbolicRef's class variable initializer begins here: // Push int value from Example1a.size. // This getstatic instruction refers to a // symbolic reference to Example1a.size. 0 getstatic #9 <Field int size> // Pop int, store into class variable // symbolicRef 3 putstatic #10 <Field int symbolicRef> // The code for localConst's class variable intializer begins here: // Expand byte operand to int, push int // result. This is the local copy of 6 bipush 70 // Example1d's length constant, 70. // Invoke Math.random(), which will push // a double return value 8 invokestatic #8 <Method double random()> 11 ldc2_w #11 <Double 3.0> // Push double constant 3.0 14 dmul // Pop two doubles, multiply, push result 15 d2i // Pop double, convert to int, push int 16 imul // Pop two ints, multiply, push int result // Pop int, store into class variable // localConst 17 putstatic #7 <Field int localConst> 20 return // Return void from <clinit> methodThe
getstatic
instruction at offset zero uses a symbolic reference (in constant pool
entry nine) to the size
field of class Example1a
. The
bipush
instruction at offset six is followed by a byte that contains the constant value
represented by Example1d.length
. Example1e
's constant
pool contains no symbolic reference to anything in class Example1d
.
Interfaces may also be awarded a
method in the class file. All fields
declared in an interface are implicitly public, static, and final and must be initialized with a field initializer. If
an interface has any field initializers that don't resolve at compile-time to a constant, that interface will have
a
method. Here's an example:
// On CD-ROM in file classlife/ex1/Example1f.java interface Example1f { int ketchup = 5; int mustard = (int) (Math.random() * 5.0); }The Java compiler generates the following
()
method for interface
Example1f
:
// The code for mustard's class variable initializer begins here // Invoke Math.random(), which will push // a double return value 0 invokestatic #6 <Method double random()> 3 ldc2_w #7 <Double 5.0> // Push double constant 2.0 6 dmul // Pop two doubles, multiply, push result 7 d2i // Pop double, convert to int, push int // Pop int, store into class variable // mustard 8 putstatic #5 <Field int mustard> 11 return // Return void from <clinit> methodNote that only the
mustard
field is initialized by this
()
method. Because the ketchup
field is initialized to
a compile-time constant, it is treated specially by the compiler. Although types that use
Example1f.mustard
will contain a symbolic reference to the field, types that use
Example1f.ketchup
will contain a local copy of ketchup
's
constant value, 5.
As mentioned previously, the Java virtual machine initializes types on their first active use. Only six activities constitute an active use: creating a new instance of a class, invoking a static method declared in a class, accessing a non-constant static field declared in a class or interface, the invocation of certain reflective methods in the Java API, the initialization of a subclass of a class, and the designation of a class as the initial class when a Java virtual machine starts up.
A use of a non-constant static field is an active use of only the class or interface that actually declares the field. For example, a field declared in a class may be referred to via a subclass. A field declared in an interface may be referred to via a subinterface or class that implements the interface. These are passive uses of the subclass, subinterface, or class that implements the interface--uses that won't trigger their initialization. They are an active use only of the class or interface in which the field is actually declared. Here's an example that illustrates this principle:
// On CD-ROM in file classlife/ex2/NewParent.java class NewParent { static int hoursOfSleep = (int) (Math.random() * 3.0); static { System.out.println("NewParent was initialized."); } } // On CD-ROM in file classlife/ex2/NewbornBaby.java class NewbornBaby extends NewParent { static int hoursOfCrying = 6 + (int) (Math.random() * 2.0); static { System.out.println("NewbornBaby was initialized."); } } // On CD-ROM in file classlife/ex2/Example2.java class Example2 { // Invoking main() is an active use of Example2 public static void main(String[] args) { // Using hoursOfSleep is an active use of NewParent, // but a passive use of NewbornBaby int hours = NewbornBaby.hoursOfSleep; System.out.println(hours); } static { System.out.println("Example2 was initialized."); } }
In the above example, executing main()
of Example2
causes only Example2
and NewParent
to be initialized.
NewbornBaby
is not initialized and need not be loaded. The following text is printed
to the standard output:
Example2 was initialized. NewParent was initialized. 2
A use of a field that is both static
and final
, and initialized
by a compile-time constant expression, is not an active use of the type that declares the field. As mentioned
above, the Java compiler resolves references to such fields to a local copy of the constant value that resides
either in the referring class's constant pool, in its bytecode streams, or both. Here's an example that
illustrates this special treatment of static final fields:
// On CD-ROM in file classlife/ex3/Angry.java interface Angry { String greeting = "Grrrr!"; int angerLevel = Dog.getAngerLevel(); } // On CD-ROM in file classlife/ex3/Dog.java class Dog { static final String greeting = "Woof, woof, world!"; static { System.out.println("Dog was initialized."); } static int getAngerLevel() { System.out.println("Angry was initialized"); return 1; } } // On CD-ROM in file classlife/ex3/Example3.java class Example3 { // Invoking main() is an active use of Example3 public static void main(String[] args) { // Using Angry.greeting is a passive use of Angry System.out.println(Angry.greeting); // Using Dog.greeting is a passive use of Dog System.out.println(Dog.greeting); } static { System.out.println("Example3 was initialized."); } }
Running the Example3
application yields the following output:
Example3 was initialized. Grrrr! Woof, woof, world!
Had Angry
been initialized, the string "Angry was
initialized."
would have been written to the standard output. Likewise, had
Dog
been initialized, the string "Dog was initialized."
would have been written to the standard output. As you can see from the above output, neither interface
Angry
or class Dog
were ever initialized during the execution of
the Example3
application.
For more information about this special treatment of static final variables, see Chapter 8, "The Linking Model."
Sponsored Links
|