8.2 InitializersInitializers can be employed for initialization of fields in objects and classes, resulting in the fields being assigned initial values. These initializers are
The rest of this section provides details on these initializers,
Field Initializer ExpressionsInitialization 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
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
Since a class is always initialized before it can be
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
Initializer Expression Execution in Textual OrderWhen 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
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
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
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.
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 ExceptionsInitializer 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 BlocksJava 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
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 BlocksJust 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
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 StateObject 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:
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)
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
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
|