Przyjrzyjmy się kolejności wykonywania kodu w blokach inicjalizacyjnych (statycznych i niestatycznych), konstruktorach oraz inicjalizacji pól statycznych i niestatycznych. W praktyce zrozumiemy, wykonując kod.

Na wejściu mamy klasę z pełnym zestawem wszystkich możliwych elementów:


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.");
    }
}

Teraz obok tej klasy utworzymy kolejną z główną metodą i uruchomimy ją:


public class Solution {
    public static void main(String args[]) {
        System.out.println("hello");
    }
}
W danych wyjściowych nie ma nic z klasy MyClass. Ponieważ nie było wywołań do MyClass, klasa w ogóle nie została załadowana. Spróbujmy teraz wywołać statyczną metodę print() klasy MyClass. Dwa razy.

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

Wniosek:

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

Wykonano tylko statyczne bloki inicjalizacji i zainicjowano pole statyczne. I zdarzyło się to tylko raz. Faktem jest, że podczas drugiego wywołania metody print() klasa była już załadowana. Pamiętaj: pola statyczne i bloki inicjalizacyjne są wykonywane jednorazowo podczas pierwszej interakcji z klasą.

Należy zauważyć, że bloki statyczne są wykonywane, a pola inicjowane w kolejności, w jakiej zostały zadeklarowane.

Następnie, zamiast wywoływać metodę statyczną, spróbujmy stworzyć dwa obiekty naszej klasy:


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

Wniosek:

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.

Najpierw statyczne bloki i pola przechodzą raz, a następnie za każdym razem, gdy tworzony jest obiekt, przetwarzane są niestatyczne bloki, pola i konstruktor. A jeśli pola i bloki inicjalizacyjne są przetwarzane w kolejności, w jakiej zostały zadeklarowane, to konstruktor jest przetwarzany na końcu, bez względu na to, gdzie jest zadeklarowany.

Skomplikujmy przykład i weźmy dwie klasy, z których jedna dziedziczy drugą:


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";
    }
}

Stwórzmy dwa obiekty klasy potomnej:


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

Wniosek:

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.

Z nowego widzimy, że statyczne bloki i zmienne klasy nadrzędnej działają przed statycznymi blokami i zmiennymi klasy potomnej. Tak samo jest z niestatycznymi blokami, zmiennymi i konstruktorami: najpierw klasa nadrzędna, potem klasa potomna. Dlaczego jest to potrzebne, możesz spojrzeć na przykład pola childNonStatic1 klasy potomnej. Aby go zainicjować, używana jest metoda klasy nadrzędnej, a ta metoda wykorzystuje odpowiednio zmienną klasy nadrzędnej, podczas inicjalizacji pola childNonStatic1 , klasa nadrzędna wraz z jej metodami musi być już załadowana, a zmienne klasy nadrzędnej muszą zostać zainicjowane.

W praktyce możesz nie natknąć się na klasy, które zawierają wszystkie wymienione elementy na raz, ale warto zapamiętać, co jest inicjowane dla czego. I to często pada w wywiadach.