When a Java class is loaded and used, its initialization blocks execute in a specific sequence: static initialization block, instance initialization block (construction code block), then constructor. However, there are nuances when dealing with final static fields or nested classes.
Consider the folllowing example. We define classes ClassA, ClassB, and ClassC, where ClassC extends ClassB. Each class contains a static initializer, an instance initializer, and a constructor that print messages:
public class ClassA {
public static void main(String[] args) {
System.out.println(ClassC.str);
}
static {
System.out.println("staticClassA");
}
{
System.out.println("instanceClassA");
}
ClassA() {
System.out.println("constructorClassA");
}
}
class ClassB {
static {
System.out.println("staticClassB");
}
{
System.out.println("instanceClassB");
}
ClassB() {
System.out.println("constructorClassB");
}
}
class ClassC extends ClassB {
public static String str = "ClassC";
static {
System.out.println("staticClassC");
}
{
System.out.println("instanceClassC");
}
ClassC() {
System.out.println("constructorClassC");
}
}
Case 1: Accessing a static field of the subclass
When we run System.out.println(ClassC.str);, the output is:
staticClassA
staticClassB
staticClassC
ClassC
Notice that the static initializers for all classes in the hierarchy execute, even though we only accessed a static field in ClassC. This is because accessing any static member of a class triggers its initialization, and since ClassC extends ClassB, the superclass must be initialized first.
Case 2: Creating an instance of the subclass
If we create an instance of ClassC:
ClassC c = new ClassC();
System.out.println(c.str);
The output becomes:
staticClassA
staticClassB
staticClassC
ClassC
----------------------------
instanceClassB
constructorClassB
instanceClassC
constructorClassC
ClassC
Static initializers run once per class loading, while instance initializers and constructors run each time an object is instantiated. The instance initializer of the parenet runs before the child's constructor.
Case 3: Declaring the static field as final
If we change the field to public static final String str = "ClassC";, the output is:
staticClassA
ClassC
----------------------------
When the static field is a compile-time constant (primitive or String and initialized with a literal), the Java compiler inlines its value. Therefore, reading the field does not require loading the class, and the static initializers of ClassC and ClassB do not execute.
Key points:
- Static initializers run when the class is first loaded, typically triggered by access to a static field or method, or by creating the first instance.
- Instance initializers run before the constructor each time an instance is created.
- Constructors run after instance initializers.
- If a subclass is initialized, its superclass must be initialized first.
- A
final staticfield with a compile-time constant value does not trigger class initialization. - Accessing a static inner class does not cause the outer class to initialize.