I recently came across this at work. While I am not sure it is really a good idea, I don't understand how static blocks are handled by the compiler.
Here is an example:
Consider that you have classes A
and B
:
public class A {
public final static List<Integer> list;
static {
list = new ArrayList<>();
}
}
public class B {
public final static int dependsOnA;
static {
dependsOnA = A.list.size();
}
}
And a main class that just reads B.dependsOnA
.
The static block in B
is dependent of the one in A
, since it uses the list
static variable.
Now, the code executes properly and no NullPointerException
is raised at runtime. But what is the mechanism that ensures that list
is initialized before it is potentially used elsewhere?
Unlike C++, Java supports a special block, called a static block (also called static clause) that can be used for static initialization of a class. This code inside the static block is executed only once: the first time the class is loaded into memory.
There is an order in which static block/method/variable gets initialized. Static Blocks are called even before the main method which is nothing but a static method i.e. execution point of every class.
In a Java class, a static block is a set of instructions that is run only once when a class is loaded into memory. A static block is also called a static initialization block. This is because it is an option for initializing or setting up the class at run-time.
A class can have any number of static initialization blocks that will execute in the same sequence as written in the program. That is, the order of execution of multiple static initialization blocks is executed automatically from top to bottom during the dot class file loading.
The mechanism is described in detail here, but the five most important points are:
implements
clause.These rules completely define the order in which static blocks are executed.
Your case is rather simple: before you access B.dependsOnA
, B
needs to be initialised (rule 1), the static initialiser is then trying to access A.list
, which triggers the initialisation of class A
(again rule 1).
Note that there's nothing stopping you from creating circular dependencies this way, which will cause interesting things to happen:
public class Bar {
public static int X = Foo.X+1;
public static void main(String[] args) {
System.out.println( Bar.X+" "+Foo.X); //
}
}
class Foo {
public static int X = Bar.X+1;
}
The result here is 2 1
because the way the initialisation happens is this:
Bar
s initialisation begins.Bar.X
s initial value is evaluated, which requires initialising Foo
firstFoo
s initialisation begins.Foo.X
s initial value is evaluated, but since Bar
s initialisation is already in progress, it isn't initialised again, Bar.X
s "current" value is used, which is 0, thus Foo.X
is initialised to 1.Bar.X
s value, Foo.X
is 1 so Bar.X
becomes 2.This works even if both fields were declared final
.
The moral of the story is to be careful with static initialisers referring to other classes in the same library or application (referring to classes in a third party library or standard class library is safe as they won't be referring back to your class).
The "mechanism" is the JVM's classloader, which will ensure that a class' initalization blocks are executed (with a global lock across the whole JVM) before returning control flow to where the class was first referenced. It will first load class A
only after referenced, in this case when B
's init block references A.list
.
During execution of the static
block of B
, the runtime encounters A
for the first time, and it will invoke the static
block of A
before it accesses A.list
.
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