1. Що таке блоки ініціалізації
У Java є два типи блоків ініціалізації:
- Нестатичні (звичайні) блоки ініціалізації — виконуються під час кожного створення нового об’єкта, одразу після ініціалізації полів і до виклику конструктора.
- Статичні блоки ініціалізації — виконуються один раз під час завантаження класу в пам’ять (до створення першого об’єкта або звернення до статичних полів/методів).
Нестатичний блок ініціалізації
Оголошується просто в тілі класу, без будь-яких слів на кшталт static:
public class User {
private String name;
// Нестатичний блок ініціалізації
{
System.out.println("Виконується нестатичний блок!");
name = "Імʼя за замовчуванням";
}
public User() {
System.out.println("Виконується конструктор!");
}
}
Статичний блок ініціалізації
Оголошується з ключовим словом static:
public class Config {
public static String appName;
static {
System.out.println("Виконується статичний блок!");
appName = "Мій суперзастосунок";
}
}
2. Порядок ініціалізації: хто головний?
У Java порядок ініціалізації елементів класу — це не просто «навмання», а суворо визначена послідовність. Якщо ви коли-небудь намагалися зібрати меблі з IKEA без інструкції, то розумієте, чому порядок важливий: переплутаєте кроки — й отримаєте не шафу, а артоб’єкт.
Порядок ініціалізації:
- Статичні поля і статичні блоки — у порядку їх оголошення в класі. Виконуються один раз під час завантаження класу.
- Нестатичні поля і нестатичні блоки — у порядку їх оголошення, під час кожного створення нового об’єкта.
- Конструктор — виконується після всіх нестатичних ініціалізацій.
Схематично
+-------------------------------+
| Завантаження класу в JVM |
+-------------------------------+
| 1. Статичні поля |
| 2. Статичні блоки |
| ↓ |
| Створення об’єкта |
| ↓ |
| 3. Нестатичні поля |
| 4. Нестатичні блоки |
| 5. Конструктор |
+-------------------------------+
Приклад з виведенням
Напишімо клас, який покаже, у якому порядку все відбувається:
public class Demo {
static String staticField = print("1. static поле");
static {
print("2. static блок");
}
String field = print("3. нестатичне поле");
{
print("4. нестатичний блок");
}
public Demo() {
print("5. конструктор");
}
static String print(String msg) {
System.out.println(msg);
return msg;
}
public static void main(String[] args) {
System.out.println("Створюємо перший об’єкт Demo:");
Demo d1 = new Demo();
System.out.println("\nСтворюємо другий об’єкт Demo:");
Demo d2 = new Demo();
}
}
Що буде на екрані?
1. static поле
2. static блок
Створюємо перший об’єкт Demo:
3. нестатичне поле
4. нестатичний блок
5. конструктор
Створюємо другий об’єкт Demo:
3. нестатичне поле
4. нестатичний блок
5. конструктор
Зверніть увагу: статичні частини (static) виконуються лише один раз — під час першого звернення до класу. Усе, що не static, — щоразу під час створення об’єкта.
3. Приклади коду: навіщо потрібні блоки ініціалізації
Коли конструктора не вистачає
Іноді частина ініціалізації має бути спільною для всіх конструкторів. Наприклад, у класі кілька конструкторів, і ви не хочете копіювати одну й ту саму ініціалізацію в кожен із них. У цьому разі зручно винести її в нестатичний блок:
public class Person {
private String id;
private String name;
{
// Цей код виконається перед будь-яким конструктором
id = java.util.UUID.randomUUID().toString();
System.out.println("Генеруємо унікальний id: " + id);
}
public Person() {
System.out.println("Person() без параметрів");
}
public Person(String name) {
this.name = name;
System.out.println("Person(String name)");
}
}
Результат: під час створення будь-якого об’єкта Person спершу буде згенеровано id, а потім виконається потрібний конструктор.
Ініціалізація складних статичних даних
Статичний блок часто використовують для ініціалізації «важких» або складних статичних полів, наприклад, для читання конфігурації з файлу, створення колекцій, під’єднання до бази даних тощо.
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", "uk");
System.out.println("Статичний блок Settings: налаштування за замовчуванням");
}
}
4. Корисні нюанси
Коли використовувати блоки ініціалізації
Коли варто використовувати
- Для спільної ініціалізації, яка потрібна в усіх конструкторах.
- Для складних статичних даних, які не можна виразити простим присвоєнням.
- Для ініціалізації статичних ресурсів (наприклад, читання конфігураційного файлу під час запуску застосунку).
Коли НЕ варто використовувати
- Якщо можна обійтися звичайним присвоєнням або конструктором — використовуйте їх.
- Не варто «ховати» бізнес-логіку в блоках ініціалізації — це ускладнює читання та підтримку коду.
- Якщо ініціалізація залежить від параметрів конструктора, використовуйте сам конструктор.
Не зловживайте блоками ініціалізації
Блоки ініціалізації — потужний, але не найуживаніший інструмент. У більшості випадків достатньо простого присвоєння або конструктора. Якщо в класі надто багато блоків ініціалізації, код стає нечітким і складним для підтримки.
Не використовуйте нестатичний блок для логіки, що залежить від параметрів конструктора
У нестатичному блоці не можна використовувати параметри конструктора, оскільки він виконується до конструктора. Якщо вам потрібно щось ініціалізувати на основі параметрів, робіть це в самому конструкторі.
Статичні блоки та наслідування
Статичні блоки ініціалізації не успадковуються. У кожного класу — свій статичний блок. Під час завантаження дочірнього класу спочатку виконається статичний блок базового класу, потім — статичний блок дочірнього.
5. Типові помилки під час роботи з блоками ініціалізації
Помилка № 1: Очікування, що нестатичний блок бачить параметри конструктора.
Багато новачків намагаються використовувати параметри конструктора в нестатичному блоці, але отримують помилку компіляції або неочікуваний результат. Пам’ятайте: нестатичний блок виконується до конструктора, а отже, жодних параметрів ще немає.
Помилка № 2: Забагато логіки в блоках ініціалізації.
Якщо в блоках ініціалізації з’являється складна логіка, код стає заплутаним. Краще виконувати основну роботу в конструкторі або окремих методах.
Помилка № 3: Кілька статичних блоків і порядок їх оголошення.
Якщо в класі кілька статичних блоків, вони виконуються в тому порядку, у якому оголошені в коді, разом зі статичними полями. Іноді це призводить до неочікуваних результатів, якщо, наприклад, один блок залежить від результату іншого.
Помилка № 4: Очікування, що статичний блок виконуватиметься під час кожного створення об’єкта.
static-блок виконується лише один раз — під час завантаження класу. Якщо ви розраховуєте на повторну ініціалізацію, цього не станеться.
Помилка № 5: Спроба звертатися до нестатичних полів зі статичного блоку.
У статичному блоці доступні лише статичні змінні та методи. Спроба звернутися до нестатичних (звичайних) полів викличе помилку компіляції.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ