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 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 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 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.