Constructors perform initialization tasks for objects. Their counterpart from a class initialization perspective is the class initializer.
A class initializer is a static-prefixed block that is introduced into a class body. It is used to initialize a loaded class via a sequence of statements. For example, I once used a class initializer to load a custom database driver class. Listing 2-16 shows the loading details.
Listing 2-16. Loading a database driver via a class initializer class JDBCFilterDriver implements Driver
{
static private Driver d;
static {
// Attempt to load JDBC-ODBC Bridge Driver and register that // driver.
try {
Class c = Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
d = (Driver) c.newInstance();
DriverManager.registerDriver(new JDBCFilterDriver());
}
catch (Exception e) {
System.out.println(e);
} }
CHAPTER 2 DISCOVERING CLASSES AND OBJECTS
//...
}
Listing 2-16’s JDBCFilterDriver class uses its class initializer to load and instantiate the class that describes Java’s JDBC-ODBC Bridge Driver, and to register a JDBCFilterDriver instance with Java’s database driver. Although this listing’s JDBC-oriented code is probably meaningless to you right now, the listing illustrates the usefulness of class initializers. (I discuss JDBC in Chapter 9.)
A class can declare a mix of class initializers and class field initializers, as demonstrated in Listing 2-17.
Listing 2-17. Mixing class initializers with class field initializers class C
{
static {
System.out.println("class initializer 1");
}
static int counter = 1;
static {
System.out.println("class initializer 2");
System.out.println("counter = "+counter);
} }
Listing 2-17 declares a class named C that specifies two class initializers and one class field initializer. When the Java compiler compiles into a classfile a class that declares at least one class initializer or class field initializer, it creates a special void <clinit>() class method that stores the bytecode equivalent of all class initializers and class field initializers in the order they occur (from top to bottom).
■ Note <clinit>
is not a valid Java method name, but is a valid name from the runtime perspective. The angle brackets were chosen as part of the name to prevent a name conflict with any
clinit()methods that you might declare in the class.
For class C, <clinit>() would first contain the bytecode equivalent of System.out.println("class initializer 1");, it would next contain the bytecode equivalent of static int counter = 1;, and it would finally contain the bytecode equivalent of System.out.println("class initializer 2");
System.out.println("counter = "+counter);.
When class C is loaded into memory, <clinit>() executes immediately and generates the following output:
class initializer 1 class initializer 2 counter = 1
CHAPTER 2 DISCOVERING CLASSES AND OBJECTS
Instance Initializers
Not all classes can have constructors, as you will discover in Chapter 3 when I present anonymous classes. For these classes, Java supplies the instance initializer to take care of instance initialization tasks.
An instance initializer is a block that is introduced into a class body, as opposed to being introduced as the body of a method or a constructor. The instance initializer is used to initialize an object via a sequence of statements, as demonstrated in Listing 2-18.
Listing 2-18. Initializing a pair of arrays via an instance initializer class Graphics
{
double[] sines;
double[] cosines;
{
sines = new double[360];
cosines = new double[sines.length];
for (int i = 0; i < sines.length; i++) {
sines[i] = Math.sin(Math.toRadians(i));
cosines[i] = Math.cos(Math.toRadians(i));
} } }
Listing 2-18’s Graphics class uses an instance initializer to create an object’s sines and cosines arrays, and to initialize these arrays’ elements to the sines and cosines of angles ranging from 0 through 359 degrees. It does so because it’s faster to read array elements than to repeatedly call Math.sin() and Math.cos() elsewhere; performance matters. (Chapter 4 introduces Math.sin() and Math.cos().)
A class can declare a mix of instance initializers and instance field initializers, as shown in Listing 2-19.
Listing 2-19. Mixing instance initializers with instance field initializers class C
{ {
System.out.println("instance initializer 1");
}
int counter = 1;
{
System.out.println("instance initializer 2");
System.out.println("counter = "+counter);
} }
Listing 2-19 declares a class named C that specifies two instance initializers and one instance field initializer. When the Java compiler compiles a class into a classfile, it creates a special void <init>() method representing the default noargument constructor when no constructor is explicitly declared;
otherwise, it create an <init>() method for each encountered constructor. Furthermore, it stores in each constructor the bytecode equivalent of all instance initializers and instance field initializers in the order they occur (from top to bottom).
CHAPTER 2 DISCOVERING CLASSES AND OBJECTS
■ Note <init>
is not a valid Java method name, but is a valid name from the runtime perspective. The angle brackets were chosen as part of the name to prevent a name conflict with any
init()methods that you might declare in the class.
For class C, <init>() would first contain the bytecode equivalent of System.out.println("instance initializer 1");, it would next contain the bytecode equivalent of int counter = 1;, and it would finally contain the bytecode equivalent of System.out.println("instance initializer 2");
System.out.println("counter = "+counter);.
When new C() executes, <init>() executes immediately and generates the following output:
instance initializer 1 instance initializer 2 counter = 1
■ Note You should rarely need to use the instance initializer, which is not commonly used in industry.
Initialization Order
A class’s body can contain a mixture of class field initializers, class initializers, instance field initializers, instance initializers, and constructors. (You should prefer constructors to instance field initializers, although I am guilty of not doing so consistently, and restrict your use of instance initializers to anonymous classes.) Furthermore, class fields and instance fields initialize to default values.
Understanding the order in which all of this initialization occurs is necessary to preventing confusion, so check out Listing 2-20.
Listing 2-20. A complete initialization demo class InitDemo
{
static double double1;
double double2;
static int int1;
int int2;
static String string1;
String string2;
static {
System.out.println("[class] double1 = "+double1);
System.out.println("[class] int1 = "+int1);
System.out.println("[class] string1 = "+string1);
System.out.println();
} {
CHAPTER 2 DISCOVERING CLASSES AND OBJECTS
System.out.println("[instance] double2 = "+double2);
System.out.println("[instance] int2 = "+int2);
System.out.println("[instance] string2 = "+string2);
System.out.println();
System.out.println("InitDemo() called");
System.out.println();
System.out.println("[class] double3 = "+double3);
System.out.println();
} {
System.out.println("[instance] double4 = "+double3);
System.out.println();
}
public static void main(String[] args) {
System.out.println ("main() started");
System.out.println();
System.out.println("[class] double1 = "+double1);
System.out.println("[class] double3 = "+double3);
System.out.println("[class] int1 = "+int1);
System.out.println("[class] string1 = "+string1);
System.out.println();
for (int i = 0; i < 2; i++) {
System.out.println("About to create InitDemo object");
System.out.println();
InitDemo id = new InitDemo();
System.out.println("id created");
System.out.println();
System.out.println("[instance] id.double2 = "+id.double2);
System.out.println("[instance] id.double4 = "+id.double4);
System.out.println("[instance] id.int2 = "+id.int2);
System.out.println("[instance] id.string2 = "+id.string2);
CHAPTER 2 DISCOVERING CLASSES AND OBJECTS
System.out.println();
} } }
Listing 2-20’s InitDemo class declares two class fields and two instance fields for the double precision floating-point primitive type, one class field and one instance field for the integer primitive type, and one class field and one instance field for the String reference type. It also introduces one explicitly initialized class field, one explicitly initialized instance field, three class initializers, three instance initializers, and one constructor. If you compile and run this code, you will observe the following output:
[class] double1 = 0.0 [class] int1 = 0 [class] string1 = null [class] double3 = 10.0 main() started
[class] double1 = 1.0 [class] double3 = 10.0 [class] int1 = 1000000000 [class] string1 = abc
About to create InitDemo object [instance] double2 = 0.0 [instance] int2 = 0 [instance] string2 = null [instance] double4 = 10.0 InitDemo() called
id created
[instance] id.double2 = 1.0 [instance] id.double4 = 10.0 [instance] id.int2 = 1000000000 [instance] id.string2 = abc About to create InitDemo object [instance] double2 = 0.0 [instance] int2 = 0 [instance] string2 = null [instance] double4 = 10.0 InitDemo() called
CHAPTER 2 DISCOVERING CLASSES AND OBJECTS
id created
[instance] id.double2 = 1.0 [instance] id.double4 = 10.0 [instance] id.int2 = 1000000000 [instance] id.string2 = abc
As you study this output in conjunction with the aforementioned discussion of class initializers and instance initializers, you will discover some interesting facts about initialization:
• Class fields initialize to default or explicit values just after a class is loaded.
Immediately after a class loads, all class fields are zeroed to default values. Code within the <clinit>() method performs explicit initialization.
• All class initialization occurs prior to the <clinit>() method returning.
• Instance fields initialize to default or explicit values during object creation. When new allocates memory for an object, it zeroes all instance fields to default values.
Code within an <init>() method performs explicit initialization.
• All instance initialization occurs prior to the <init>() method returning.
Additionally, because initialization occurs in a top-down manner, attempting to access the contents of a class field before that field is declared, or attempting to access the contents of an instance field before that field is declared causes the compiler to report an illegal forward reference.
Inheriting State and Behaviors
We tend to categorize stuff by saying things like “cars are vehicles” or “savings accounts are bank accounts.” By making these statements, we really are saying that cars inherit vehicular state (e.g., make and color) and behaviors (e.g., park and display mileage), and that savings accounts inherit bank account state (e.g., balance) and behaviors (e.g., deposit and withdraw). Car, vehicle, savings account, and bank account are examples of real-world entity categories, and inheritance is a hierarchical
relationship between similar entity categories in which one category inherits state and behaviors from at least one other entity category. Inheriting from a single category is called single inheritance, and
inheriting from at least two categories is called multiple inheritance.
Java supports single inheritance and multiple inheritance to facilitate code reuse—why reinvent the wheel? Java supports single inheritance in a class context, in which a class inherits state and behaviors from another class through class extension. Because classes are involved, Java refers to this kind of inheritance as implementation inheritance.
Java supports multiple inheritance only in an interface context, in which a class inherits behavior templates from one or more interfaces through interface implementation, or in which an interface inherits behavior templates from one or more interfaces through interface extension. Because interfaces are involved, Java refers to this kind of inheritance as interface inheritance. (I discuss interfaces later in this chapter.)
This section introduces you to Java’s support for implementation inheritance by first focusing on class extension. It then introduces you to a special class that sits at the top of Java’s class hierarchy. After introducing you to composition, which is an alternative to implementation inheritance for reusing code, this section shows you how composition can be used to overcome problems with implementation inheritance.
CHAPTER 2 DISCOVERING CLASSES AND OBJECTS