10.8 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 length is first created at (1); its creation is 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.
Declaration Order of Instance Initializers
Analogous to the other initializers discussed earlier, an instance initializer block cannot make a forward reference to a field by its simple name in a read operation as that would violate the declaration-before-reading rule. However, using the this keyword to access a field is not a problem.
The class below has an instance initializer block at (1) with forward references to the fields i, j, and k that are declared at (7), (8), and (9), respectively. These fields are accessed using the this reference in read operations at (3), (4), (5), and (6). Using the simple name of these fields at (3), (4), (5), and (6) to access their values will violate the declare-before-use rule, resulting in compile-time errors—regardless of whether the fields are declared with initializer expressions or not, or whether they are final or not. The fields i and j are accessed at (2) in write operations, which are permitted using the simple name. However, care must be exercised to ensure that the fields are initialized correctly. At (3), (4), and (5), the fields i and j have the value 10. However, when the initializer expressions are evaluated in the instance field declarations, the value of j will be set to 100.
public class InstanceInitializersII {
{ //Instance initializer with forward references. (1)
i = j = 10; // (2) Permitted.
int result = this.i * this.j; // (3) i is 10, j is 10.
System.out.println(this.i); // (4) 10
System.out.println(this.j); // (5) 10
System.out.println(this.k); // (6) 50
}
// Instance field declarations.
int i; // (7) Field declaration without initializer expression.
int j = 100; // (8) Field declaration with initializer expression.
final int k = 50; // (9) Final instance field with constant expression.
}
Example 10.6 illustrates some additional subtle points regarding instance initializer blocks. In Example 10.6, 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) occurs after the declaration, and therefore, is allowed. Forward reference made on the left-hand side of the assignment is always allowed, as shown at (2), (3), (5), and (7).
Declaring local variables using the reserved word var in instance initializer blocks is shown at (5) and (12) in Example 10.6.
Example 10.6 Instance Initializers and Forward References
public 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.
var 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.
var e = nsf1 = 50; // (12) OK. Assignment to nsf1 allowed.
}
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
As in an instance initializer expression, the keywords this and super can be used to refer to the current object in an instance initializer block.
As in a static initializer block, 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 usage of an instance initializer block is in anonymous classes (§9.6, p.521), which cannot declare constructors, but can instead use instance initializer blocks to initialize fields. In Example 10.7, the anonymous class defined at (1) uses an instance initializer block at (2) to initialize its fields.
Example 10.7 Instance Initializer Block in Anonymous Class
// File: InstanceInitBlock.java
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;
}
@Override
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
Leave a Reply