Declaration Order of Static Initializers
In the class ScheduleV1 below, the static field declaration at (1) has a forward reference to the static field numOfWeeks which has not been declared yet. The simple name of the static field numOfWeeks cannot be used in the initializer expression at (1) before its declaration.
public class ScheduleV1 {
//static int numOfDays = 7 * numOfWeeks; // (1) Compile-time error! Simple name.
static int numOfWeeks = 52; // (2)
}
The code will compile if we change the order of the declarations, and the static fields numOfWeeks and numOfDays will be initialized correctly with the values 52 and 364, respectively.
public class ScheduleV2 {
static int numOfWeeks = 52; // (2) 52
static int numOfDays = 7 * numOfWeeks; // (1) 364
}
The code will also compile if the class name is used to access the field, but the static field numOfDays will not be initialized correctly.
public class ScheduleV3 {
static int numOfDays = 7 * ScheduleV3.numOfWeeks; // (1) 0
static int numOfWeeks = 52; // (2) 52
}
The code above is actually executed as follows, with the default value 0 of the static field numOfWeeks being used in the initializer expression at (1):
public class ScheduleV3 {
static int numOfDays; // Default value: 0
static int numOfWeeks; // Default value: 0
static {
numOfDays = 7 * numOfWeeks; // (1) 0
numOfWeeks = 52; // (2) 52
}
}
However, the static field numOfDays will be initialized correctly if the static field numOfWeeks is declared final.
public class ScheduleV4 {
static int numOfDays = 7 * ScheduleV4.numOfWeeks; // (1) 364
static final int numOfWeeks = 52; // (2) final: 52
}
The initializer expression 52 for the static field numOfWeeks at (2) is a constant expression of type int, which is an int literal. The compiler is able to compute the value of a constant expression. A final variable of either a primitive type or type String that is initialized with a constant expression is called a constant variable—that is, once initialized, the value of a final variable cannot be changed (§5.5, p. 230). At (2), the final static field numOfWeeks is a constant variable. During class initialization, such final static fields are always initialized first, before any other initializers are executed. Such a constant field never gets initialized with the default value of its type at runtime. Rearrangement of code done by the compiler in this case is equivalent to the following code:
public class ScheduleV4 {
static final int numOfWeeks = 52; // Constant variable: 52
static int numOfDays; // Default value: 0
static {
numOfDays = 7 * numOfWeeks; // (1) 364
}
}
Static fields should be accessed statically—that is, using the class name—which is the best policy, but care should be exercised in the order of their declaration and initialization.
When making forward references using simple names, code in a static initializer is subject to the declaration-before-reading rule. Note that this rule applies only when the use of the field is by its simple name. Using the class name to access a static field is never a problem.
Example 10.3 illustrates forward references and the order of execution for static initializer expressions in field declarations and code in 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 declaration order. A static field has the value it was last assigned in an initializer. If there is no explicit assignment, the static field has the default value of its type. Referring to a static field using the class name is always allowed.
Declaring local variables using the reserved word var in static initializer blocks can be found at (5) and (12) in Example 10.3.
Example 10.3 Static Initializers and Forward References
package refs1;
public class ForwardRefs {
static { // (1) Static initializer block
System.out.printf(“Enter static block 1: sf1=%s, sf2=%s%n”,
ForwardRefs.sf1, ForwardRefs.sf2); // Enter static block 1: sf1=0, sf2=0
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
var b = sf1 = 20; // (5) OK. Assignment to sf1 allowed
int c = ForwardRefs.sf1; // (6) OK. Not accessed by simple name
System.out.printf(“Exit static block 1: sf1=%s, sf2=%s%n”,
ForwardRefs.sf1, ForwardRefs.sf2); // Exit static block 1: sf1=20, sf2=0
}
// Field declarations:
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
System.out.printf(“Enter static block 2: sf1=%s, sf2=%s%n”,
ForwardRefs.sf1, ForwardRefs.sf2); // Enter static block 2: sf1=30, sf2=30
int d = 2 * sf1; // (11) OK. Read operation after declaration
var e = sf1 = 50; // (12) OK. Assignment to sf1 allowed
System.out.printf(“Exit static block 2: sf1=%s, sf2=%s%n”,
ForwardRefs.sf1, ForwardRefs.sf2); // Exit static block 2: sf1=50, sf2=30
}
public static void main(String[] args) {
}
}
Output from the program:
Enter static block 1: sf1=0, sf2=0
Exit static block 1: sf1=20, sf2=0
Enter static block 2: sf1=30, sf2=30
Exit static block 2: sf1=50, sf2=30
Example 10.4 gives an idea of the code rearrangement that the compiler does to facilitate the initialization for the class in Example 10.3. Field declarations from Example 10.3 are arranged first at (1) and (2) in Example 10.4, followed by a single static initializer block at (3). The static initializer block in Example 10.4 contains the code for the first static initializer block, followed by the initializer expression from the first static field declaration statement, and lastly the code from the second static initializer block in the order these initializers are declared in Example 10.3.
During class initialization, first the declarations of the static fields sf1 and sf2 at (1) and (2), respectively, result in them being created and initialized to their default value 0. Not surprisingly, execution of the code in the static initializer block at (3) gives the same result as in Example 10.3.
Example 10.4 Static Initializers and Order of Execution
package refs2;
public class ForwardRefsSimulated {
// Declaration of static fields:
static int sf1; // (1) Initialized to default value: 0
static int sf2; // (2) Initialized to default value: 0
static { // (3) Static initializer block
// Code from static block 1:
System.out.printf(“Enter static block 1: sf1=%s, sf2=%s%n”,
sf1, sf2); // Enter static block 1: sf1=0, sf2=0
sf1 = 10; // (4) sf1 gets the value 10
var b = sf1 = 20; // (5) b and sf1 get the value 20
int c = sf1; // (6) c gets the value 20
System.out.printf(“Exit static block 1: sf1=%s, sf2=%s%n”,
sf1, sf2); // Exit static block 1: sf1=20, sf2=0
// Initializer expressions for field declaration:
sf1 = sf2 = 30; // (7) sf1 and sf2 get the value 30
// Code from static block 2:
System.out.printf(“Enter static block 2: sf1=%s, sf2=%s%n”,
sf1, sf2); // Enter static block 2: sf1=30, sf2=30
int d = 2 * sf1; // (8) d gets the value 60
var e = sf1 = 50; // (9) e and sf1 get the value 50
System.out.printf(“Exit static block 2: sf1=%s, sf2=%s%n”,
sf1, sf2); // Exit static block 2: sf1=50, sf2=30
}
public static void main(String[] args) {
}
}
Output from the program:
Enter static block 1: sf1=0, sf2=0
Exit static block 1: sf1=20, sf2=0
Enter static block 2: sf1=30, sf2=30
Exit static block 2: sf1=50, sf2=30
Leave a Reply