Hãy xem thứ tự mã được thực thi trong các khối khởi tạo (tĩnh và không tĩnh), các hàm tạo và khởi tạo các trường tĩnh và không tĩnh. Chúng tôi sẽ điều tra những gì xảy ra trong thực tế, bằng cách thực thi mã.

Để bắt đầu, chúng ta có một lớp với tập hợp đầy đủ mọi phần tử có thể:


public class MyClass {
    static {
        System.out.println("Static Block #1.");
    }

    public static String staticField = setStaticField();

    public MyClass() {
        System.out.println("Constructor.");
    }

    static {
        System.out.println("Static Block #2.");
    }

    {
        System.out.println("Initialization Block #1.");
    }

    public String nonStaticField = setNonStaticField();

    {
        System.out.println("Initialization Block #2.");
    }

    private String setNonStaticField() {
        System.out.println("Non-static field.");
        return "nonStaticField";
    }

    private static String setStaticField() {
        System.out.println("Static field.");
        return "staticField";
    }

    public static void print() {
        System.out.println("print() method.");
    }
}

Bây giờ, bên cạnh lớp này, chúng ta sẽ tạo một lớp khác với phương thức chính và sau đó chạy nó:


public class Solution {
    public static void main(String args[]) {
        System.out.println("hello");
    }
}

Đầu ra không bao gồm gì từ MyClass class. There were no calls to MyClass methods, so the class was not even loaded. Let's now try to call the static print() method of the MyClass class. Twice.


public class Solution {
    public static void main(String args[]) {
        MyClass.print();
        MyClass.print();
    }
}

Output:

Static Block #1.
Static field.
Static Block #2.
print() method.
print() method.

Only the static initialization blocks were executed and the static field was initialized. And this happened only once. That's because the class was already loaded when we made the second call to the print() method. Remember: static fields are initialized and initialization blocks are executed once upon the first interaction with the class.

Note that static blocks are executed and fields are initialized in the order they are declared.

Next, instead of calling a static method, let's try creating two objects of our class:


public class Solution {
    public static void main(String args[]) {
        new MyClass();
        System.out.println();
        new MyClass();
    }
}

Output:

Static Block #1.
Static field.
Static Block #2.
Initialization Block #1.
Non-static field.
Initialization Block #2.
Constructor.

Initialization Block #1.
Non-static field.
Initialization Block #2.
Constructor.

First, static blocks are executed and static fields are initialized once. After that, each time an object is created, the non-static blocks, fields, and a constructor are processed. The fields and initialization blocks are processed in the order of their declaration, but the constructor is processed at the end — no matter where it is declared.

Let's complicate the example — we'll take two classes, where one of them inherits the other:


public class ParentClass {
    static {
        System.out.println("Static Block #1 of the parent class.");
    }

    public static String parentStatic = setParentStatic();

    static {
        System.out.println("Static Block #2 of the parent class.");
    }

    {
        System.out.println("Initialization Block #1 of the parent class.");
    }

    public String parentNonStatic = setParentNonStatic();

    {
        System.out.println("Initialization Block #2 of the parent class.");
    }

    public ParentClass() {
        System.out.println("Constructor of the parent class.");
    }

    private String setParentNonStatic() {
        System.out.println("Non-static field of the parent class.");
        return "parentNonStatic";
    }

    private static String setParentStatic() {
        System.out.println("Static field of the parent class.");
        return "parentStatic";
    }

    public String setChildNonStatic1() {
        System.out.println("Non-static field of the child class #1.");
        return "childNonStatic2" + parentNonStatic;
    }
}
 
public class ChildClass extends ParentClass {
    static {
        System.out.println("Static Block #1 of the child class.");
    }

    public static String childStatic = setChildStatic();

    static {
        System.out.println("Static Block #2 of the child class.");
    }

    public String childNonStatic1 = setChildNonStatic1();

    {
        System.out.println("Initialization Block #1 of the child class.");
    }

    public String childNonStatic2 = setChildNonStatic2();

    {
        System.out.println("Initialization Block #2 of the child class.");
    }

    public ChildClass() {
        System.out.println("Constructor of the child class.");
    }

    private String setChildNonStatic2() {
        System.out.println("Non-static field of the child class #2.");
        return "childNonStatic";
    }

    private static String setChildStatic() {
        System.out.println("Static field of the child class.");
        return "childStatic";
    }
}

Let's create two objects of the child class:


public class Solution {
    public static void main(String[] args) {
        new ChildClass();
        System.out.println();
        new ChildClass();
    }
}

Output:

Static Block #1 of the parent class.
Static field of the parent class.
Static Block #2 of the parent class.
Static Block #1 of the child class.
Static field of the child class.
Static Block #2 of the child class.
Initialization Block #1 of the parent class.
Non-static field of the parent class.
Initialization Block #2 of the parent class.
Constructor of the parent class.
Non-static field of the child class #1.
Initialization Block #1 of the child class.
Non-static field of the child class #2.
Initialization Block #2 of the child class.
Constructor of the child class.

Initialization Block #1 of the parent class.
Non-static field of the parent class.
Initialization Block #2 of the parent class.
Constructor of the parent class.
Non-static field of the child class #1.
Initialization Block #1 of the child class.
Non-static field of the child class #2.
Initialization Block #2 of the child class.
Constructor of the child class.

From the new output, we see that the static blocks and variables of the parent class are processed before the static blocks and variables of the child class. It's the same with non-static blocks and variables and constructors: first the parent class, then the child class. To understand why this is necessary, take a look at the example of the childNonStatic1 field of the child class. A method of the parent class is used to initialize it, and that method in turn uses a variable of the parent class. That means that when the childNonStatic1 field is initialized, the parent class and its methods must already be loaded, and the variables of the parent class must be initialized.

In practice, you may not encounter classes that include all of these elements all at once, but it will be useful for you to remember what gets initialized before what. Oh, and this is also often asked about in job interviews.