The mechanism is described in detail here, but the five most important points are:
- Before a class is referenced, it needs to be initialised.
- If initialisation of a class has already begun (or if it’s finished), it isn’t attempted again.
- Before a class is initialised, all its superclasses and superinterfaces need to be initialised first.
- Static initialisers within a single class are executed in textual order.
- Implemented interfaces are initialised in the order in which they appear in the
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 initialisingFoo
firstFoo
s initialisation begins.Foo.X
s initial value is evaluated, but sinceBar
s initialisation is already in progress, it isn’t initialised again,Bar.X
s “current” value is used, which is 0, thusFoo.X
is initialised to 1.- We’re back to evaluating
Bar.X
s value,Foo.X
is 1 soBar.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).