1. Czym są bloki inicjalizacji
W Javie istnieją dwa typy bloków inicjalizacji:
- Niestatyczne (zwykłe) bloki inicjalizacji – wykonywane przy każdym tworzeniu nowego obiektu, zaraz po inicjalizacji pól i przed wywołaniem konstruktora.
- Statyczne bloki inicjalizacji – wykonywane raz podczas ładowania klasy do pamięci (przed utworzeniem pierwszego obiektu lub odwołaniem do statycznych pól/metod).
Blok inicjalizacji niestatyczny
Deklaruje się go po prostu w ciele klasy, bez żadnych słów typu static:
public class User {
private String name;
// Niestatyczny blok inicjalizacji
{
System.out.println("Wykonywany jest blok niestatyczny!");
name = "Nazwa domyślna";
}
public User() {
System.out.println("Wykonywany jest konstruktor!");
}
}
Blok inicjalizacji statyczny
Deklaruje się go ze słowem kluczowym static:
public class Config {
public static String appName;
static {
System.out.println("Wykonywany jest blok statyczny!");
appName = "Moja superaplikacja";
}
}
2. Kolejność inicjalizacji: kto tu rządzi?
W Javie kolejność inicjalizacji elementów klasy to nie „od czapy”, lecz ściśle określona sekwencja. Jeśli kiedykolwiek próbowaliście złożyć meble z IKEA bez instrukcji, zrozumiecie, dlaczego taka kolejność jest ważna: jeśli pomieszać kroki, zamiast szafy wyjdzie obiekt artystyczny.
Kolejność inicjalizacji:
- Pola statyczne i statyczne bloki – w kolejności ich deklaracji w klasie. Wykonywane są raz przy ładowaniu klasy.
- Pola niestatyczne i bloki niestatyczne – w kolejności ich deklaracji, przy każdym tworzeniu nowego obiektu.
- Konstruktor – wykonywany po wszystkich niestatycznych inicjalizacjach.
Schematycznie
+-------------------------------+
| Ładowanie klasy w JVM |
+-------------------------------+
| 1. Pola statyczne |
| 2. Bloki statyczne |
| ↓ |
| Tworzenie obiektu |
| ↓ |
| 3. Pola niestatyczne |
| 4. Bloki niestatyczne |
| 5. Konstruktor |
+-------------------------------+
Przykład z wypisem
Napiszmy klasę, która pokaże, w jakiej kolejności wszystko się dzieje:
public class Demo {
static String staticField = print("1. static pole");
static {
print("2. static blok");
}
String field = print("3. niestatyczne pole");
{
print("4. niestatyczny blok");
}
public Demo() {
print("5. konstruktor");
}
static String print(String msg) {
System.out.println(msg);
return msg;
}
public static void main(String[] args) {
System.out.println("Tworzymy pierwszy obiekt Demo:");
Demo d1 = new Demo();
System.out.println("\nTworzymy drugi obiekt Demo:");
Demo d2 = new Demo();
}
}
Co zobaczymy na ekranie?
1. static pole
2. static blok
Tworzymy pierwszy obiekt Demo:
3. niestatyczne pole
4. niestatyczny blok
5. konstruktor
Tworzymy drugi obiekt Demo:
3. niestatyczne pole
4. niestatyczny blok
5. konstruktor
Zwróć uwagę: części statyczne (static) wykonują się tylko raz – przy pierwszym odwołaniu do klasy. Wszystko, co nie jest static, – za każdym razem przy tworzeniu obiektu.
3. Przykłady kodu: po co są bloki inicjalizacji
Gdy konstruktor nie wystarcza
Zdarza się, że część inicjalizacji powinna być wspólna dla wszystkich konstruktorów. Na przykład klasa ma kilka konstruktorów i nie chcesz kopiować tej samej inicjalizacji do każdego z nich. W takim przypadku wygodnie jest wynieść ją do bloku niestatycznego:
public class Person {
private String id;
private String name;
{
// Ten kod zostanie wykonany przed każdym konstruktorem
id = java.util.UUID.randomUUID().toString();
System.out.println("Generujemy unikalne id: " + id);
}
public Person() {
System.out.println("Person() bez parametrów");
}
public Person(String name) {
this.name = name;
System.out.println("Person(String name)");
}
}
Wynik: podczas tworzenia dowolnego obiektu Person najpierw zostanie wygenerowany id, a potem wykona się odpowiedni konstruktor.
Inicjalizacja złożonych danych statycznych
Bloku statycznego często używa się do inicjalizacji „ciężkich” lub złożonych pól statycznych, np. wczytywania konfiguracji z pliku, tworzenia kolekcji, połączenia z bazą itd.
public class Settings {
public static final java.util.Map<String, String> DEFAULTS;
static {
DEFAULTS = new java.util.HashMap<>();
DEFAULTS.put("theme", "light");
DEFAULTS.put("language", "ru");
System.out.println("Statyczny blok Settings: ustawienia domyślne");
}
}
4. Przydatne niuanse
Kiedy używać bloków inicjalizacji
Kiedy warto używać
- Do wspólnej inicjalizacji, potrzebnej we wszystkich konstruktorach.
- Do złożonych danych statycznych, których nie da się wyrazić prostym przypisaniem.
- Do inicjalizacji zasobów statycznych (np. wczytanie pliku konfiguracyjnego przy starcie aplikacji).
Kiedy NIE warto używać
- Jeśli wystarczy zwykłe przypisanie albo konstruktor – użyj ich.
- Nie „ukrywaj” logiki biznesowej w blokach inicjalizacji – utrudnia to czytanie i utrzymanie kodu.
- Jeśli inicjalizacja zależy od parametrów konstruktora, użyj samego konstruktora.
Nie nadużywaj bloków inicjalizacji
Bloki inicjalizacji to potężne, ale raczej rzadko używane narzędzie. W większości przypadków wystarczy proste przypisanie albo konstruktor. Jeśli w klasie jest zbyt wiele bloków inicjalizacji, kod staje się nieczytelny i trudny w utrzymaniu.
Nie używaj bloku niestatycznego do logiki zależnej od parametrów konstruktora
W bloku niestatycznym nie można używać parametrów konstruktora, ponieważ wykonuje się on PRZED konstruktorem. Jeśli musisz coś zainicjalizować na podstawie parametrów, zrób to w samym konstruktorze.
Bloki statyczne a dziedziczenie
Statyczne bloki inicjalizacji nie są dziedziczone. Każda klasa ma własny blok statyczny. Przy ładowaniu klasy pochodnej najpierw wykona się blok statyczny klasy bazowej, a potem blok statyczny klasy pochodnej.
5. Typowe błędy przy pracy z blokami inicjalizacji
Błąd nr 1: Oczekiwanie, że blok niestatyczny „widzi” parametry konstruktora.
Wielu początkujących próbuje używać parametrów konstruktora w bloku niestatycznym, ale dostaje błąd kompilacji albo nieoczekiwany rezultat. Pamiętaj: blok niestatyczny wykonuje się PRZED konstruktorem, więc parametrów jeszcze nie ma.
Błąd nr 2: Za dużo logiki w blokach inicjalizacji.
Jeśli w blokach inicjalizacji pojawia się złożona logika, kod staje się zagmatwany. Lepiej wykonać główną pracę w konstruktorze lub osobnych metodach.
Błąd nr 3: Kilka bloków statycznych i kolejność ich deklaracji.
Jeśli w klasie jest kilka bloków statycznych, wykonują się one w takiej kolejności, w jakiej zostały zadeklarowane w kodzie, razem z polami statycznymi. Czasem prowadzi to do nieoczekiwanych rezultatów, jeśli np. jeden blok zależy od wyniku innego.
Błąd nr 4: Oczekiwanie, że blok statyczny wykona się przy każdym tworzeniu obiektu.
static-blok wykonuje się tylko raz – przy ładowaniu klasy. Jeśli liczysz na ponowną inicjalizację, to się nie wydarzy.
Błąd nr 5: Próba odwoływania się do pól niestatycznych z bloku statycznego.
W bloku statycznym dostępne są tylko zmienne i metody statyczne. Próba odwołania się do pól niestatycznych (zwykłych) spowoduje błąd kompilacji.
GO TO FULL VERSION