8.2 Initializers


Initializers can be employed for initialization of fields in objects and classes, resulting in the fields being assigned initial values. These initializers are

  • field initializer expressions

  • s tatic initializer blocks

  • instance initializer blocks

The rest of this section provides details on these initializers, concluding with a discussion on the phases involved in constructing the state of an object, when the object is created by using the new operator.

Field Initializer Expressions

Initialization of fields can be explicitly specified in field declaration statements using initializer expressions. The value of the initializer expression must be assignment compatible to the declared field (see Section 3.4, p. 47 and Section 6.6, p. 260). We distinguish between static and non-static field initializers.

 class ConstantInitializers {     int minAge = 12;                    // (1) Non-static     static double pensionPoints = 10.5; // (2) Static     // ... } 

The fields of an object are initialized with the values of initializer expressions when the object is created by using the new operator. In the previous example, the declaration at (1) will result in the field minAge being initialized to 12 in every object of the class ConstantInitializers created with the new operator. If no explicit initializer expressions are specified, default values (see Section 2.4, p. 33) are assigned to the fields.

Class initialization results in the static fields of a class being initialized with the values of the initializer expressions. The declaration at (2) will result in the static field pensionPoints being initialized to 10.5 when the class is initialized. Again, if no explicit initializers are specified, default values are assigned to the static fields.

An initializer expression for a static field cannot refer to non-static members by their simple names . The keywords this and super cannot occur in a static initializer expression.

Since a class is always initialized before it can be instantiated , an instance initializer expression can always refer to any static member of a class, regardless of the member declaration order. In the following code, the instance initializer expression at (1) refers to the static field NO_OF_WEEKS declared and initialized at (2). Such a forward reference is legal. More examples of forward references are given in the next subsection.

 class MoreInitializers {            int noOfDays    = 7 * NO_OF_WEEKS;    // (1) Non-static     static int NO_OF_WEEKS = 52;                 // (2) Static     // ... } 

Initializer expressions can also be used to define constants in interfaces (see Section 6.4, p. 255). Such initializer expressions are implicitly static, as they define values of final static fields.

Initializer expressions are also used to initialize local variables (see Section 2.3, p. 31). A local variable is initialized with the value of the initializer expression every time the local variable declaration is executed.

Initializer Expression Execution in Textual Order

When an object is created using the new operator, instance initializer expressions are executed in the order in which the instance fields are declared in the class.

Java requires that the declaration of a field must occur before its usage in any initializer expression, if the field is used on the right-hand side of an assignment in the initializer expression. This essentially means that the declaration of a field must occur before the value of the field is read in an initializer expression. Using the field on the left-hand side of an assignment in the initializer expression does not violate the declaration-before-read rule, as this constitutes a write operation. This rule applies when the usage of the field is by its simple name .

There is one caveat to the declaration-before-read rule: it does not apply if the initializer expression defines an anonymous class, as the usage then occurs in a different class, which has its own accessibility rules in the enclosing context. Restrictions outlined earlier help to detect initialization anomalies at compile time.

In the next example, the initialization at (2) generates a compile-time error, because the field width in the initializer expression violates the declaration-before-read rule. The usage of the field width in the initializer expression at (2) does not occur on the left-hand side of the assignment. This is an illegal forward reference. To remedy the situation, the declaration of the field width at (4) can be moved in front of the declaration at (2). In any case, we can use the keyword this as shown at (3), but this will read the default value in the field width .

 class NonStaticInitializers {     int length  = 10;                   // (1) //  double area = length * width;       // (2) Not Ok. Illegal forward reference.     double area = length * this.width;  // (3) Ok, but width has default value 0.     int width   = 10;                   // (4)     int sqSide = height = 20;           // (5) OK. Legal forward reference.     int height;                         // (6) } 

The forward reference at (5) is legal. The usage of field height in the initializer expression at (5) occurs on the left-hand side of the assignment. The initializer expression at (5) is evaluated as (sqSide = (height = 20)) . Every object of class NonStaticInitializers will have the field height set to the value 20 .

The declaration-before-read rule is equally applicable to static initializer expressions when static fields are referenced by their simple name.

Example 8.4 shows why the order of field initializer expressions can be important. The initializer expressions in Example 8.4 are calls to methods defined in the class. Methods are not subject to the same access rules as initializer expressions. The call at (2) to the method initMaxGuests() defined at (4) is expected to return the maximum number of guests. However, the field occupancyPerRoom at (3) will not have been explicitly initialized; therefore, its default value ( ) will be used in the method initMaxGuests() , which will return an incorrect value. The program output shows that after object creation the occupancy per room is correct, but the maximum number of guests is wrong.

Example 8.4 Initializer Expression Order and Method Calls
 class Hotel {     private int noOfRooms        = 12;                // (1)     private int maxNoOfGuests    = initMaxGuests();   // (2) Bug     private int occupancyPerRoom = 2;                 // (3)     public int initMaxGuests() {                      // (4)         System.out.println("occupancyPerRoom: " +                            occupancyPerRoom);         System.out.println("maxNoOfGuests: " +                            noOfRooms * occupancyPerRoom);         return noOfRooms * occupancyPerRoom;     }     public int getMaxGuests() {                       // (5)         return maxNoOfGuests;     }     public int getOccupancy() {                       // (6)         return occupancyPerRoom;     } } public class TestOrder {     public static void main(String[] args) {         Hotel hotel = new Hotel();                   // (7)         System.out.println("After object creation: ");         System.out.println("occupancyPerRoom: " +                            hotel.getOccupancy());    // (8)         System.out.println("maxNoOfGuests: " +                            hotel.getMaxGuests());    // (9)     } } 

Output from the program:

 occupancyPerRoom: 0 maxNoOfGuests: 0 After object creation: occupancyPerRoom: 2 maxNoOfGuests: 0 
Initializer Expressions and Checked Exceptions

Initializer expressions in named classes and interfaces must not result in any uncaught checked exception (see Section 5.9, p. 201). If any checked exception is thrown during execution of an initializer expression, it must be caught and handled by code called from the initializer expression. This restriction does not apply to instance initializer expressions in anonymous classes.

Example 8.5 illustrates exception handling for initializer expressions in named classes. The static initializer expression at (3) calls the static method createHotelPool() at (4), which can catch and handle the checked TooManyHotelsException defined at (2). If the method createHotelPool() uses the throws clause to specify the checked exception, instead of catching and handling it within a try - catch block, then the initializer expression at (3), which called the method, must handle the exception. However, the syntax of the initializer expression does not allow any exception handling to be specified, and the compiler complains that the checked exception is not handled.

The instance initializer expression at (5) calls the method initMaxGuests() at (6), which can throw the unchecked RoomOccupancyTooHighException . If thrown, this exception will be caught and handled in the main() method. Program output confirms that an unchecked RoomOccupancyTooHighException was thrown during program execution.

Example 8.5 Exceptions in Initializer Expressions
 class RoomOccupancyTooHighException       extends RuntimeException {}                    // (1) Unchecked Exception class TooManyHotelsException       extends Exception {}                           // (2) Checked Exception class Hotel {     // Static Members     private static int noOfHotels = 12;     private static Hotel[] hotelPool = createHotelPool();   // (3)     private static Hotel[] createHotelPool() {              // (4)         try {              if (noOfHotels > 10)                  throw new TooManyHotelsException();         } catch (TooManyHotelsException e) {              noOfHotels = 10;              System.out.println("No. of hotels adjusted to " +                                  noOfHotels);         }         return new Hotel[noOfHotels];     }     // Instance Members     private int noOfRooms        = 215;     private int occupancyPerRoom = 5;     private int maxNoOfGuests    = initMaxGuests();         // (5)     private int initMaxGuests() {                           // (6)         if (occupancyPerRoom > 4)                  throw new RoomOccupancyTooHighException();         return noOfRooms * occupancyPerRoom;     } } public class ExceptionsInInitializers {     public static void main(String[] args) {         try { new Hotel();}         catch (RoomOccupancyTooHighException exception) {              exception.printStackTrace();         }     } } 

Output from the program:

 No. of hotels adjusted to 10 RoomOccupancyTooHighException       at Hotel.initMaxGuests(ExceptionsInInitializers.java:29)       at Hotel.<init>(ExceptionsInInitializers.java:25)       at ExceptionsInInitializers.main(ExceptionsInInitializers.java:36) 

Static Initializer Blocks

Java allows static initializer blocks to be defined in a class. Although such blocks can include arbitrary code, they are primarily used for initializing static fields. The code in a static initializer block is executed once only when the class is initialized.

The syntax of a static initializer block consists of the keyword static followed by a local block that can contain arbitrary code as shown at (3).

 class StaticInitializers {     final static int ROWS = 12, COLUMNS = 10;          // (1)     static long[][] matrix = new long[ROWS][COLUMNS];  // (2)     // ...     static {                                           // (3) Static Initializer         for (int i = 0; i < matrix.length; i++)             for (int j = 0; j < matrix[i].length; j++)                 matrix[i][j] = 2*i + j;     }     // ... } 

When the class StaticInitializers is first loaded in the previous example, the final static fields at (1) are initialized. Then the array of arrays matrix of specified size is created at (2), followed by the execution of the static block at (3).

If a class relies on native method implementations , a static initializer can be used to load any external libraries that the class needs (see Section 4.10, p. 148).

Note that the static initializer block is not contained in any method. A class can have more than one static initializer block. Initializer blocks are not members of a class nor can they have a return statement, as they cannot be called directly.

When a class is initialized, the initializer expressions in static field declarations and static initializer blocks are executed in the order they are specified in the class. In the previous example, the initializer expressions at (1) and (2) are executed before the static initializer block at (3).

Similar restrictions apply to static initializer blocks as for static initializer expressions: the keywords this and super cannot occur in a static initializer block.

When making forward references using simple names, code in a static initializer block is also subject to the declaration-before-read rule discussed in the previous subsection. Example 8.6 illustrates forward references and the order of execution for static initializer expressions and static initializer blocks. An illegal forward reference occurs at (4), where an attempt is made to read the value of the field sf1 before its declaration. At (11) the read operation is after the declaration and, therefore, allowed. Forward reference made on the left-hand side of the assignment is always allowed, as shown at (2), (5), and (7). The initializers are executed in their textual order. A static field has the value it was last assigned in an initializer. If there is no explicit assignment, the field has the default value of its type.

Example 8.6 Static Initializers and Forward References
 class StaticForwardReferences {     static {              // (1) Static initializer block         sf1 = 10;         // (2) OK. Assignment to sf1 allowed     //  sf1 = if1;        // (3) Not OK. Non-static field access in static context     //  int a = 2 * sf1;  // (4) Not OK. Read operation before declaration         int b = sf1 = 20; // (5) OK. Assignment to sf1 allowed         int c = StaticForwardReferences.sf1;// (6) OK. Not accessed by simple name     }     static int sf1 = sf2 = 30;  // (7) Static field. Assignment to sf2 allowed     static int sf2;             // (8) Static field     int if1 = 5;                // (9) Non-static field     static {              // (10) Static initializer block         int d = 2 * sf1;  // (11) OK. Read operation after declaration         int e = sf1 = 50; // (12)     }     public static void main(String[] args) {         System.out.println("sf1: " + StaticForwardReferences.sf1);         System.out.println("sf2: " + StaticForwardReferences.sf2);     } } 

Output from the program:

 sf1: 50 sf2: 30 

Exception handling in static initializer blocks is no different from that in static initializer expressions: execution cannot allow an uncaught checked exception. Example 8.7 shows a static initializer block at (3) that catches and handles a checked exception in the try-catch block at (4). A static initializer block cannot be called directly, therefore, any checked exceptions must be caught and handled in the body of the static initializer block.

Example 8.7 also shows a static initializer block at (5) that throws an unchecked exception at (6) during class initialization. As the program output shows, this exception is handled by the default exception handler, resulting in termination of the program.

Example 8.7 Static Initializer Blocks and Exceptions
 class BankrupcyException       extends RuntimeException {}               // (1) Unchecked Exception class TooManyHotelsException       extends Exception {}                      // (2) Checked Exception class Hotel {     // Static Members     private static boolean bankrupt   = true;     private static int     noOfHotels = 11;     private static Hotel[] hotelPool;     static {                                    // (3) Static block         try {                                   // (4) Handles checked exception              if (noOfHotels > 10)                  throw new TooManyHotelsException();         } catch (TooManyHotelsException e) {              noOfHotels = 10;              System.out.println("No. of hotels adjusted to " +                                  noOfHotels);         }         hotelPool = new Hotel[noOfHotels];     }     static {                                    // (5) Static block         if (bankrupt)             throw new BankrupcyException();     // (6) Throws unchecked exception     }     // ... } public class ExceptionInStaticInitBlocks {     public static void main(String[] args) {         new Hotel();     } } 

Output from the program:

 No. of hotels adjusted to 10 Exception in thread "main" java.lang.ExceptionInInitializerError         at ExceptionInStaticInitBlocks.main(ExceptionInStaticInitBlocks.java:33) Caused by: BankrupcyException         at Hotel.<clinit>(ExceptionInStaticInitBlocks.java:26) 

Instance Initializer Blocks

Just as static initializer blocks can be used to initialize static fields in a named class, Java provides the ability to initialize fields during object creation using instance initializer blocks. In this respect, such blocks serve the same purpose as constructors during object creation. The syntax of an instance initializer block is the same as that of a local block, as shown at (2) in the following code. The code in the local block is executed every time an instance of the class is created.

 class InstanceInitializers {     long[] squares = new long[10];    // (1)     // ...     {                                 // (2) Instance Initializer         for (int i = 0; i < squares.length; i++)             squares[i] = i*i;     }     // ... } 

The array squares of specified size is created first at (1), followed by the execution of the instance initializer block at (2) every time an instance of the class InstanceInitializers is created. Note that the instance initializer block is not contained in any method. A class can have more than one instance initializer block, and these (and any instance initializer expressions in instance field declarations) are executed in the order they are specified in the class.

Analogous to other initializers discussed so far, an instance initializer block cannot make a forward reference to a field that violates the declaration-before-read rule. In Example 8.8, an illegal forward reference occurs in the code at (4), which attempts to read the value of the field nsf1 before it is declared. The read operation at (11) is after the declaration and is, therefore, allowed. Forward reference made on the left-hand side of the assignment is always allowed, as shown at (2), (3), (5), and (7).

Example 8.8 Instance Initializers and Forward References
 class NonStaticForwardReferences {     {                      // (1) Instance initializer block         nsf1 = 10;         // (2) OK. Assignment to nsf1 allowed         nsf1 = sf1;        // (3) OK. Static field access in non-static context     //  int a = 2 * nsf1;  // (4) Not OK. Read operation before declaration         int b = nsf1 = 20; // (5) OK. Assignment to nsf1 allowed         int c = this.nsf1; // (6) OK. Not accessed by simple name     }     int nsf1 = nsf2 = 30;  // (7) Non-static field. Assignment to nsf2 allowed     int nsf2;              // (8) Non-static field     static int sf1 = 5;    // (9) Static field     {                      // (10) Instance initializer block         int d = 2 * nsf1;  // (11) OK. Read operation after declaration         int e = nsf1 = 50; // (12)     }     public static void main(String[] args) {         NonStaticForwardReferences objRef = new NonStaticForwardReferences();         System.out.println("nsf1: " + objRef.nsf1);         System.out.println("nsf2: " + objRef.nsf2);     } } 

Output from the program:

 nsf1: 50 nsf2: 30 

Similar to instance initializer expressions, the keywords this and super can be used to refer to the current object in an instance initializer block. As with static initializer blocks, the return statement is also not allowed in instance initializer blocks.

An instance initializer block can be used to factor out common initialization code that will be executed regardless of which constructor is invoked.

A typical use of an instance initializer block is in anonymous classes (see Section 7.5, p. 308), which cannot declare constructors, and instead can use instance initializer blocks to initialize fields. In Example 8.9, the anonymous class defined at (1) uses an instance initializer block defined at (2) to initialize its fields.

Example 8.9 Instance Initializer Block in Anonymous Class
 class Base {     protected int a;     protected int b;     void print() {         System.out.println("a: " + a);     } } class AnonymousClassMaker {     Base createAnonymous() {         return new Base() {           // (1) Anonymous class             {                         // (2) Instance initializer                 a = 5; b = 10;             }             void print() {                 super.print();                 System.out.println("b: " + b);             }         };  // end anonymous class     } } public class InstanceInitBlock {     public static void main(String[] args) {         new AnonymousClassMaker().createAnonymous().print();     } } 

Output from the program:

 a: 5 b: 10 

Exception handling in instance initializer blocks is similar to that in static initializer blocks. Example 8.10 shows an instance initializer block at (3) that catches and handles a checked exception in the try-catch block at (4). Another instance initializer block at (5) throws an unchecked exception at (6). The runtime system handles the exception, printing the stack trace and terminating the program.

Exception handling in instance initializer blocks differs from that in static initializer blocks in the following respect: the execution of an instance initializer block can result in an uncaught checked exception, provided the exception is declared in the throws clause of every constructor in the class. Static initializer blocks cannot allow this, since no constructors are involved in class initialization. Instance initializer blocks in anonymous classes have greater freedom: they can throw any exception.

Example 8.10 Exception Handling in Instance Initializer Blocks
 class RoomOccupancyTooHighException       extends Exception {}                      // (1) Checked exception class BankrupcyException       extends RuntimeException {}               // (2) Unchecked exception class Hotel {     // Instance Members     private boolean bankrupt         = true;     private int     noOfRooms        = 215;     private int     occupancyPerRoom = 5;     private int     maxNoOfGuests;     {                                           // (3) Instance block         try {              if (occupancyPerRoom > 4)          // (4) Handles checked exception                  throw new RoomOccupancyTooHighException();         } catch (RoomOccupancyTooHighException exception) {              System.out.println("ROOM OCCUPANCY TOO HIGH: " + occupancyPerRoom);              occupancyPerRoom = 4;         }         maxNoOfGuests = noOfRooms * occupancyPerRoom;     }     {                                           // (5) Instance initializer block         if (bankrupt)             throw new BankrupcyException();     // (6) Throws unchecked exception     }    // ... } public class ExceptionsInInstBlocks {     public static void main(String[] args) {         new Hotel();     } } 

Output from the program:

 ROOM OCCUPANCY TOO HIGH: 5 Exception in thread "main" BankrupcyException         at Hotel.<init>(ExceptionsInInstBlocks.java:26)         at ExceptionsInInstBlocks.main(ExceptionsInInstBlocks.java:32) 

Constructing Initial Object State

Object initialization involves constructing the initial state of an object when it is created by using the new operator. First, the fields are initialized to their default values (see Section 2.4, p. 33) ”whether they are subsequently given non-default initial values or not ”then the constructor is invoked. This can lead to local chaining of constructors. The invocation of the constructor at the end of the local chain of constructor invocations results in the following actions, before the constructor's execution resumes:

  • Implicit or explicit invocation of the superclass constructor. Constructor chaining ensures that the inherited state of the object is constructed first (see Section 6.3, p. 243).

  • Initialization of the instance fields by executing their instance initializer expressions and any instance initializer blocks in the order they are specified in the class declaration.

Example 8.11 illustrates object initialization. The new operator is used at (8) to create an object of class SubclassB . The default constructor SubclassB() at (2) uses the this() construct to locally chain to the non-default constructor at (3). It is this constructor that leads to an implicit call of the superclass constructor. As can be seen from the program output, the execution of the superclass's constructor at (1) reaches completion first. This is followed by the execution of the instance initializer block at (4) and instance initializer expression at (6). Then the execution of the body of the non-default constructor at (3) is resumed. Finally, the default constructor completes its execution, thereby completing the construction of the object state.

Note that the instance initializers are executed in the order they are specified in the class declaration. The forward reference to the field value at (5) is legal because the usage of the field value is on the left-hand side of the assignment. The default value of the field value is overwritten by the instance initializer block at (5). The field value is again overwritten by the instance initializer expression at (6), and finally by the non-default constructor at (3).

Example 8.11 Object State Construction
 class SuperclassA {     public SuperclassA() {                        // (1)         System.out.println("Constructor in SuperclassA");     } } class SubclassB extends SuperclassA {     SubclassB() {                                 // (2)         this(3);         System.out.println("Default constructor in SubclassB");     }     SubclassB(int i) {                            // (3)         System.out.println("Non-default constructor in SubclassB");         value = i;     }     {                                             // (4)         System.out.println("Instance initializer block in SubclassB");         value = 2;                                // (5)     }     int value = initializerExpression();          // (6)     private int initializerExpression() {         // (7)         System.out.println("Instance initializer expression in SubclassB");         return 1;     } } public class ObjectConstruction {     public static void main(String[] args) {         SubclassB objRef = new SubclassB();       // (8)         System.out.println("value: " + objRef.value);     } } 

Output from the program:

 Constructor in SuperclassA Instance initializer block in SubclassB Instance initializer expression in SubclassB Non-default constructor in SubclassB Default constructor in SubclassB value: 3 

Some care should be exercised when writing constructors for non-final classes, since the object that is constructed might be a subclass instance. Example 8.12 shows a situation where use of overridden methods in superclass initializers and constructors can give unexpected results. The example intentionally uses the this reference to underline the fact that the instance methods and constructors are invoked on the current object, and that the constructor call results in the initialization of the object state as we would expect.

The program output shows that the field superValue at (1) in class SuperclassA never gets initialized explicitly when an object of the SubclassB is created at (8). The SuperclassA constructor at (2) does have a call to a method called doValue() at (3). A method with such a name is defined in class SuperclassA at (4), but is also overridden in SubclassB at (7). The program output indicates that the method doValue() from the SubclassB is called at (3) in the SuperclassA constructor. The implementation of the method doValue() at (4) never gets executed when an object of the SubclassB is created. Method invocation always determines the implementation of the method to be executed, based on the actual type of the object. Keeping in mind that it is an object of SubclassB that is being initialized, it is not surprising that the call to the method named doValue at (3) results in the method from SubclassB being executed. This can lead to unintended results. The overriding method doValue() at (7) in class SubclassB can access the field value declared at (5) before its initializer expression has been executed; that is, the method invoked can access the state of the object before this has been completely initialized.

Example 8.12 Initialization under Object State Construction
 class SuperclassA {     protected int superValue;                              // (1)     SuperclassA() {                                        // (2)         System.out.println("Constructor in SuperclassA");         this.doValue();                                    // (3)     }     void doValue() {                                       // (4)         this.superValue = 911;         System.out.println("superValue: " + this.superValue);     } } class SubclassB extends SuperclassA {     private int value = 800;                               // (5)     SubclassB() {                                          // (6)         System.out.println("Constructor in SubclassB");         this.doValue();         System.out.println("superValue: " + this.superValue);     }     void doValue() {                                       // (7)         System.out.println("value: " + this.value);     } } public class ObjectInitialization {     public static void main(String[] args) {         System.out.println("Creating an object of SubclassB.");         new SubclassB();                                   // (8)     } } 

Output from the program:

 Creating an object of SubclassB. Constructor in SuperclassA value: 0 Constructor in SubclassB value: 800 superValue: 0 

Class initialization takes place before any instance of the class can be created or a static method of the class can be invoked. A superclass is initialized before its subclasses are initialized. Initializing a class involves initialization of the static fields by executing their static initializer expressions and execution of any static initializer blocks.

Initialization of an interface only involves execution of any static initializer expressions for the static fields declared in the interface. An interface cannot specify instance initializer expressions as it has no instance fields, and neither can it specify instance initializer blocks as it cannot be instantiated.



A Programmer[ap]s Guide to Java Certification
A Programmer[ap]s Guide to Java Certification
ISBN: 201596148
EAN: N/A
Year: 2003
Pages: 284

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net