Java can initialize fields during object creation using instance initializer blocks.
Initializers can be used to set initial values for fields in objects and classes. There are three kinds of initializers:
◉ Field initializer expressions
◉ Static initializer blocks
◉ Instance initializer blocks
Initialization of fields can be specified in field declaration statements using initializer expressions. The value of the initializer expression must be assignment-compatible with the declared field.
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 only once when the class is loaded and initialized.
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, and that is the subject of this article.
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 line (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 line (1); its creation is followed by the execution of the instance initializer block at line (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 in which 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 because that would violate the declare-before-reading rule. However, using the this keyword to access a field is not a problem.
The class in Listing 1 has an instance initializer block at line (1) with forward references to the fields i, j, and k, which are declared at lines (7), (8), and (9), respectively. These fields are accessed using the this reference in read operations at lines (3), (4), (5), and (6). Using the simple name of these fields at lines (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 whether they are final.
The fields i and j are accessed at line (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 lines (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.
Listing 1. Accessing fields using the keyword this
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.
}
Listing 2 illustrates some additional subtle points regarding instance initializer blocks. An illegal forward reference occurs in the code at line (4), which attempts to read the value of the field nsf1 before it is declared. The read operation at line (11) occurs after the declaration; therefore, it is allowed. Forward references made on the left side of the assignment are always allowed, as shown at lines (2), (3), (5), and (7). Meanwhile, declaring local variables using the reserved word var in instance initializer blocks is shown at lines (5) and (12).
Listing 2. 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 nonstatic 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) Nonstatic field. Assignment to nsf2 allowed.
int nsf2; // (8) Nonstatic 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);
}
}
The following is the output from the code in Listing 2:
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. (A return statement is 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, which cannot declare constructors but can instead use instance initializer blocks to initialize fields. In Listing 3, the anonymous class defined at line (1) uses an instance initializer block at line (2) to initialize its fields.
Listing 3. Instance initializer block in an 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();
}
}
The following is the output from the code in Listing 3:
a: 5
b: 10
Exception handling in instance initializer blocks
Exception handling in instance initializer blocks is similar to that in static initializer blocks. However, exception handling in instance initializer blocks differs from that in static initializer blocks in the following aspect: 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 even greater freedom: They can throw any exception.
Source: oracle.com
0 comments:
Post a Comment