I simply don't understand the following code's execution flow:
class Test {
static String s1 = getVal();
static String s2 = "S2";
private static String getVal() {
return s2;
}
public static void main(String args[]) {
System.out.println(s2); // prints S2
System.out.println(s1); // prints null
}
}
It is supposed to print S2
at second println
statement. I am more interested in understanding why this happens rather than a solution.
When static keyword is used, variable or data members or functions can not be modified again. It is allocated for the lifetime of program. Static functions can be called directly by using class name. Static variables are initialized only once.
You can define a static field using the static keyword. If you declare a static variable in a class, if you haven't initialized it, just like with instance variables compiler initializes these with default values in the default constructor. Yes, you can also initialize these values using the constructor.
But in some cases it generates code that uses two guard variables and initialize each static variable separately. The problem is when such two types of initialization are mixed in executable binary. In such case it may happen that second static variable will get initialized twice.
The only way to initialize static final variables other than the declaration statement is Static block. A static block is a block of code with a static keyword. In general, these are used to initialize the static members. JVM executes static blocks before the main method at the time of class loading.
Static things gets executed in the order they appear in the code.
static String s1 = getVal();
So starting with first line, s1
gets evaluated and by that time s2
is still null
. Hence you see the null value.
static
variables and static
initializer blocks are initialized in the order they appear in the source code (except of static final
variables that get initialized before non-final static
variables).
s1
is initialized before s2
, therefore the call to getVal()
returns the default value of s2
, which is null
.
You can change the order of the static
variables in order to initialize s2
first:
static String s2 = "S2";
static String s1 = getVal();
Another way to force the initialization of s2
to take place before that of s1
is to make s2
final:
static String s1 = getVal();
static final String s2 = "S2";
Following is an excerpt of the initialization order as stated in JLS 12.4.2. Detailed Initialization Procedure. The sections related to static
variables are highlighted.
For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation. The procedure for initializing C is then as follows:
Synchronize on the initialization lock, LC, for C. This involves waiting until the current thread can acquire LC.
If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this step.
If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally.
If the Class object for C indicates that C has already been initialized, then no further action is required. Release LC and complete normally.
If the Class object for C is in an erroneous state, then initialization is not possible. Release LC and throw a NoClassDefFoundError.
Otherwise, record the fact that initialization of the Class object for C is in progress by the current thread, and release LC.
Then, initialize the static fields of C which are constant variables(§4.12.4, §8.3.2, §9.3.1).
Next, if C is a class rather than an interface, then let SC be its superclass and let SI1, ..., SIn be all superinterfaces of C that declare at least one default method. The order of superinterfaces is given by a recursive enumeration over the superinterface hierarchy of each interface directly implemented by C (in the left-to-right order of C's implements clause). For each interface I directly implemented by C, the enumeration recurs on I's superinterfaces (in the left-to-right order of I's extends clause) before returning I.
For each S in the list [ SC, SI1, ..., SIn ], if S has not yet been initialized, then recursively perform this entire procedure for S. If necessary, verify and prepare S first.
If the initialization of S completes abruptly because of a thrown exception, then acquire LC, label the Class object for C as erroneous, notify all waiting threads, release LC, and complete abruptly, throwing the same exception that resulted from initializing S.
Next, determine whether assertions are enabled (§14.10) for C by querying its defining class loader.
Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With