1. Kolejność inicjalizacji obiektu w Javie: kto pierwszy, kto ostatni?
Tworzenie obiektu w Javie — to nie tylko „zaalokować pamięć i zapisać tam wartości”. To cały rytuał z jasno określoną kolejnością działań. Jeśli wyobrazisz sobie proces inicjalizacji jako start rakiety, to każdy stopień ma swoje ściśle określone miejsce.
Gdy piszesz:
Person john = new Person("Vasya", 30);
Java wykonuje następujące kroki:
- Alokuje pamięć dla obiektu i przypisuje wszystkim polom wartości domyślne.
- Wykonuje jawne inicjalizacje pól (jeśli wartości zostały podane bezpośrednio przy deklaracji).
- Wykonuje wszystkie niestatyczne (zwykłe) bloki inicjalizacji — w takiej kolejności, w jakiej zostały zapisane w klasie.
- Wykonuje ciało konstruktora, który został wywołany przez new.
Przyjrzyjmy się każdemu krokowi po kolei.
2. Inicjalizacja pól
Wartości domyślne
Gdy obiekt dopiero powstaje (zanim cokolwiek jawnie przypiszesz), wszystkie jego pola otrzymują wartości domyślne:
| Typ pola | Wartość domyślna |
|---|---|
|
|
|
|
|
|
|
'\u0000' (znak null) |
| Typy referencyjne (String, inne obiekty) | |
Jawna inicjalizacja
Jeśli podczas deklaracji pola od razu nadajesz mu wartość, to wartość ta zostanie ustawiona po wartościach domyślnych, ale przed wykonaniem konstruktora.
public class Person {
private String name = "Bezimienny";
private int age = 18;
}
Jeśli utworzysz obiekt przez pusty konstruktor, te wartości pozostaną. Jeśli przez konstruktor z parametrami — najprawdopodobniej zostaną nadpisane.
3. Bloki inicjalizacji: po co są i jak działają
W Javie można deklarować tak zwane niestatyczne (zwykłe) bloki inicjalizacji. Wykonują się za każdym razem przy tworzeniu obiektu, zaraz po inicjalizacji pól, ale przed konstruktorem.
Składnia:
public class Person {
{
System.out.println("Wykonywany jest niestatyczny blok inicjalizacji!");
}
}
Jeśli w klasie jest kilka takich bloków, wykonują się w takiej kolejności, w jakiej są zapisane.
Przykład z kilkoma blokami
public class Person {
private String name = "Bezimienny";
{
System.out.println("Blok 1: name = " + name);
name = "Zagadka";
}
{
System.out.println("Blok 2: name = " + name);
}
public Person() {
System.out.println("Konstruktor: name = " + name);
}
}
Jeśli utworzysz obiekt:
Person p = new Person();
Wyjście będzie następujące:
Blok 1: name = Bezimienny
Blok 2: name = Zagadka
Konstruktor: name = Zagadka
Po co są bloki inicjalizacji?
W praktyce bloki inicjalizacji są używane rzadko. Zazwyczaj wszystko, co można zrobić w bloku, da się wykonać albo przy deklaracji pola, albo w konstruktorze. Czasem jednak (np. gdy masz kilka konstruktorów, a część logiki inicjalizacji powinna być wspólna dla wszystkich) bloki inicjalizacji mogą być wygodne.
4. Konstruktor: ostatni akord inicjalizacji
Po tym, jak wszystkie pola otrzymały wartości (domyślne lub jawne) i wykonały się wszystkie bloki inicjalizacji, wywoływany jest konstruktor — ten, który wskazałeś po new.
W konstruktorze zazwyczaj ustawiasz finalne wartości pól, przyjmujesz parametry i wykonujesz inną inicjalizację zależną od danych wejściowych.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
System.out.println("Konstruktor: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
}
5. Demonstracja pełnej kolejności inicjalizacji
Napiszmy klasę, która wyraźnie pokaże, w jakiej kolejności przebiega inicjalizacja.
public class Person {
private String name = "Bezimienny";
private int age = 18;
{
System.out.println("Blok inicjalizacji: name = " + name + ", age = " + age);
age = 21;
}
public Person() {
System.out.println("Konstruktor bez parametrów: name = " + name + ", age = " + age);
}
public Person(String name, int age) {
System.out.println("Konstruktor z parametrami: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
public void printInfo() {
System.out.println("Person: name = " + name + ", age = " + age);
}
}
A teraz w naszej klasie głównej (np. Main):
public class Main {
public static void main(String[] args) {
System.out.println("Tworzymy obiekt p1:");
Person p1 = new Person();
p1.printInfo();
System.out.println("\nTworzymy obiekt p2:");
Person p2 = new Person("peter", 30);
p2.printInfo();
}
}
Wynik będzie mniej więcej taki:
Tworzymy obiekt p1:
Blok inicjalizacji: name = Bezimienny, age = 18
Konstruktor bez parametrów: name = Bezimienny, age = 21
Person: name = Bezimienny, age = 21
Tworzymy obiekt p2:
Blok inicjalizacji: name = Bezimienny, age = 18
Konstruktor z parametrami: name = Bezimienny, age = 21
Person: name = peter, age = 30
Zwróć uwagę: w konstruktorze z parametrami wartości pól są jeszcze stare (te po bloku inicjalizacji), a dopiero po konstruktorze pola otrzymują wartości finalne.
6. Diagram kolejności inicjalizacji
Oto mały schemat (schemat blokowy) do wizualizacji:
flowchart TD
A[Alokacja pamięci, wartości domyślne] --> B[Jawna inicjalizacja pól]
B --> C[Wykonanie niestatycznych bloków inicjalizacji]
C --> D[Wykonanie konstruktora]
D --> E[Obiekt gotowy do użycia]
7. Uwagi i niuanse
Pola statyczne i bloki statyczne
W tym wykładzie mówimy tylko o niestatycznych (zwykłych) polach i blokach. Pola statyczne i bloki inicjalizują się tylko raz przy ładowaniu klasy, a nie przy każdym tworzeniu obiektu. Więcej o nich powiemy w tematach o enkapsulacji.
Kiedy co się inicjalizuje?
- Pola — przy każdym utworzeniu obiektu.
- Bloki inicjalizacji — przy każdym utworzeniu obiektu.
- Pola i bloki statyczne — tylko raz przy ładowaniu klasy.
Czy można odwoływać się do pól w blokach inicjalizacji?
Tak, można! Ważne jednak, że jeśli pole jest zadeklarowane niżej niż blok, to już istnieje, ale jeśli odwołasz się do niego przed jego jawną inicjalizacją, otrzymasz wartość domyślną.
8. Typowe błędy przy inicjalizacji obiektów
Błąd nr 1: Oczekiwanie, że pola zostaną zainicjalizowane przed ich deklaracją.
W Javie kolejność deklaracji pól i bloków inicjalizacji w klasie ma znaczenie. Jeśli w bloku inicjalizacji odwołujesz się do pola, które jest zadeklarowane niżej, to już istnieje, ale nie zostało jeszcze jawnie zainicjalizowane — będzie miało wartość domyślną.
Błąd nr 2: Duplikowanie inicjalizacji w blokach i konstruktorach.
Początkujący często powtarzają tę samą logikę zarówno w bloku, jak i w konstruktorze. Lepiej użyć albo bloku, albo konstruktora, albo wywołać jeden konstruktor z drugiego przez this(...).
Błąd nr 3: Oczekiwanie, że pola statyczne będą inicjalizowane przy każdym tworzeniu obiektu.
Pola i bloki statyczne działają inaczej — inicjalizują się tylko raz przy ładowaniu klasy.
Błąd nr 4: Używanie niezainicjalizowanych pól referencyjnych.
Jeśli zapomnisz jawnie zainicjalizować pole referencyjne (na przykład, String name;), to będzie równe null dopóki nie przypiszesz mu wartości jawnie w bloku inicjalizacji lub w konstruktorze.
GO TO FULL VERSION