Блоки инициализации в Java: статический блок (static block)
Запустите программу ниже — и она выведет Статический блок инициализирован. и остановится в ожидании ввода, хотя метод main() ещё не выполнил ни одной строки. Этот код находится в статическом блоке, а JVM выполняет его раньше, чем вызовет main().
Блок инициализации в Java — это фрагмент кода в фигурных скобках, объявленный прямо в теле класса, вне методов и конструкторов. Блоки бывают двух видов: статический блок (static { ... }, static block, static initializer) выполняется один раз при инициализации класса и нужен для подготовки статических переменных, а нестатический блок ({ ... }, блок экземпляра, instance initializer) выполняется при каждом создании объекта, перед телом конструктора.
Статический блок инициализации
Если для инициализации статических переменных требуются вычисления, а не просто присваивание значения, используют статический блок. Он объявляется с помощью ключевого слова static, за которым идут фигурные скобки. Статический блок выполняется ровно один раз — когда JVM инициализирует класс, до вызова main() и до создания первого объекта. Например:
import java.util.Scanner;
public class StaticBlockExample {
static String a;
static {
System.out.println("Статический блок инициализирован.");
Scanner scanner = new Scanner(System.in);
a = scanner.nextLine();
}
public static void main(String[] args) {
System.out.println("a = " + a);
}
} Если после запуска ввести строку Hello, результат будет таким:
Статический блок инициализирован.
Hello
a = Hello Сначала выполнился статический блок и присвоил значение переменной a, и только потом JVM вызвала main(). Подробнее о самих статических переменных — в уроке «Статические переменные в Java».
На заметку
Чтение с консоли в статическом блоке хорошо показывает момент его выполнения, но в реальном коде так не делают: static-блок должен быстро и предсказуемо подготовить данные класса. Ввод пользователя, сетевые запросы и прочую «долгую» логику держите в обычных методах.
Зачем нужен статический блок
Простую статическую переменную достаточно инициализировать в объявлении: static int max = 100;. Статический блок нужен, когда одного выражения мало:
- нужно заполнить статическую коллекцию или массив в цикле;
- инициализация может выбросить исключение, которое надо обработать в
try-catch; - нужно выполнить однократную настройку класса — например, загрузить нативную библиотеку через
System.loadLibrary(...)или прочитать конфигурацию; - несколько статических полей зависят друг от друга и вычисляются вместе.
Пример: справочник кодов HTTP-статусов, который заполняется один раз при инициализации класса:
import java.util.HashMap;
import java.util.Map;
public class HttpStatus {
static final Map<Integer, String> STATUSES = new HashMap<>();
static {
STATUSES.put(200, "OK");
STATUSES.put(404, "Not Found");
STATUSES.put(500, "Internal Server Error");
}
public static void main(String[] args) {
System.out.println(STATUSES.get(404)); // Not Found
}
} Для небольших неизменяемых коллекций в современных версиях Java (9+) статический блок часто заменяют фабричным методом: static final Map<Integer, String> STATUSES = Map.of(200, "OK", 404, "Not Found");. Но если данные нужно вычислить, прочитать или обработать исключение — статический блок остаётся подходящим инструментом.
Несколько статических блоков: порядок выполнения
В классе может быть сколько угодно статических блоков. Они выполняются сверху вниз, в порядке объявления, вперемешку с инициализаторами статических полей:
public class StaticOrder {
static int a = print("поле a", 1);
static {
print("статический блок 1", 0);
}
static int b = print("поле b", 2);
static {
print("статический блок 2", 0);
}
static int print(String name, int value) {
System.out.println(name);
return value;
}
public static void main(String[] args) {
System.out.println("main: a = " + a + ", b = " + b);
}
} Результат выполнения:
поле a
статический блок 1
поле b
статический блок 2
main: a = 1, b = 2 Для читаемости обычно достаточно одного статического блока, расположенного после объявления статических полей.
Нестатический блок инициализации
Нестатический блок инициализации (блок экземпляра, instance initializer) объявляется просто фигурными скобками без static. Он выполняется при каждом создании объекта — после вызова конструктора родителя (super(...)) и перед остальным кодом конструктора. Внутри него доступны и статические, и нестатические поля, а также this.
public class Counter {
static int created;
int id;
{
created++;
id = created;
System.out.println("Блок экземпляра: id = " + id);
}
Counter() {
System.out.println("Конструктор Counter()");
}
Counter(String name) {
System.out.println("Конструктор Counter(" + name + ")");
}
public static void main(String[] args) {
new Counter();
new Counter("second");
}
} Результат выполнения:
Блок экземпляра: id = 1
Конструктор Counter()
Блок экземпляра: id = 2
Конструктор Counter(second) Компилятор фактически копирует код нестатического блока в начало каждого конструктора (сразу после super(...)). Поэтому такой блок удобен, когда у класса несколько конструкторов с общей частью. На практике то же самое чаще делают вызовом одного конструктора из другого через this(...), а блоки экземпляра встречаются в основном в анонимных классах, где конструктор объявить нельзя.
Статический и нестатический блок: сравнение
| Характеристика | Статический блок | Нестатический блок |
|---|---|---|
| Синтаксис | static { ... } | { ... } |
| Когда выполняется | Один раз при инициализации класса | При каждом создании объекта (new) |
| Относительно конструктора | Задолго до него, до создания первого объекта | После super(...), перед телом конструктора |
| Доступ к полям | Только к статическим | К статическим и нестатическим |
this и super | Недоступны | Доступны |
| Checked-исключения | Выбрасывать нельзя, только обрабатывать внутри | Можно, если их объявляют все конструкторы класса |
| Типичное применение | Заполнение статических коллекций, однократная настройка класса | Общий код нескольких конструкторов, анонимные классы |
Порядок инициализации класса и объекта
Вопрос «в каком порядке выполнятся блоки и конструкторы» — один из самых частых на собеседованиях по Java Core. Рассмотрим его на примере с наследованием:
class Parent {
static {
System.out.println("1. static-блок Parent");
}
{
System.out.println("3. блок экземпляра Parent");
}
Parent() {
System.out.println("4. конструктор Parent");
}
}
class Child extends Parent {
static {
System.out.println("2. static-блок Child");
}
{
System.out.println("5. блок экземпляра Child");
}
Child() {
System.out.println("6. конструктор Child");
}
}
public class InitOrderExample {
public static void main(String[] args) {
new Child();
System.out.println("--- второй объект ---");
new Child();
}
} Результат выполнения:
1. static-блок Parent
2. static-блок Child
3. блок экземпляра Parent
4. конструктор Parent
5. блок экземпляра Child
6. конструктор Child
--- второй объект ---
3. блок экземпляра Parent
4. конструктор Parent
5. блок экземпляра Child
6. конструктор Child Общее правило:
- Инициализация класса (один раз): статические поля и статические блоки — сначала родителя, затем наследника, в каждом классе в порядке объявления.
- Создание объекта (при каждом
new): для каждого класса иерархии, начиная с родителя, — нестатические поля и блоки экземпляра в порядке объявления, затем тело конструктора.
При создании второго объекта статические блоки уже не выполняются: класс инициализирован.
Когда выполняется статический блок
Статический блок выполняется при инициализации класса, а она происходит лениво — при первом активном использовании класса:
- создании объекта через
new; - вызове статического метода (в том числе
main()при запуске программы); - обращении к статическому полю (кроме compile-time констант
static final); - инициализации подкласса — сначала инициализируется родитель;
- явной загрузке через
Class.forName("ИмяКласса").
При этом объявление переменной типа (StaticBlockExample x;), создание массива (new StaticBlockExample[10]) или литерал StaticBlockExample.class статический блок не запускают. Класс инициализируется один раз для каждого загрузчика классов (classloader), поэтому в обычном приложении static-блок выполняется ровно один раз за всё время работы программы.
Важно
JVM гарантирует, что инициализация класса выполнится один раз даже при одновременном обращении из нескольких потоков: остальные потоки дождутся её завершения. На этом основан потокобезопасный паттерн ленивого синглтона Initialization-on-demand holder.
Неочевидные моменты
- Checked-исключения. Из статического блока нельзя выбросить проверяемое исключение — его нужно обработать внутри в
try-catch. Иначе код не скомпилируется. - ExceptionInInitializerError. Если в статическом блоке (или инициализаторе static-поля) возникнет непроверяемое исключение, например
static int value = Integer.parseInt("abc");, JVM обернёт его вExceptionInInitializerError. Класс останется неинициализированным, и каждое следующее обращение к нему завершитсяNoClassDefFoundError. - Illegal forward reference. В статическом блоке можно присвоить значение полю, объявленному ниже, но нельзя прочитать его по простому имени:
static { System.out.println(x); } static int x = 1;— ошибка компиляции. - Нет this и нестатических полей. Статический блок выполняется без объекта, поэтому в нём нельзя использовать
this,superи обращаться к нестатическим полям и методам напрямую — те же ограничения, что и у статических методов. - Тяжёлая логика. Долгие операции в static-блоке замедляют первое обращение к классу, а ошибка в них делает класс непригодным до перезапуска. Такой код сложно тестировать.
- Double brace initialization. Конструкция
new HashMap<>() {{ put("a", 1); }}— это анонимный подкласс с блоком экземпляра. Она создаёт лишний класс и удерживает ссылку на внешний объект, поэтому считается антипаттерном; используйтеMap.of(...).
Общий обзор всех вариантов использования ключевого слова static — в уроке «Ключевое слово static». Закрепить материал можно на практических задачах по static.
Часто задаваемые вопросы
Можно ли выполнить программу без метода main с помощью статического блока?
Сейчас нет. До Java 7 JVM сначала инициализировала класс, поэтому код в статическом блоке успевал выполниться, и только затем выдавалась ошибка об отсутствии main. Начиная с Java 7 лаунчер проверяет наличие метода main до инициализации класса, и статический блок без main не запустится.
Выполнится ли статический блок при обращении к константе static final?
Если это compile-time константа (примитив или String, инициализированный константным выражением, например static final int MAX = 10;), — нет: компилятор подставляет значение прямо в место использования, и класс не инициализируется. Если же значение вычисляется во время выполнения (static final Integer MAX = 10; или static final long START = System.currentTimeMillis();), обращение к полю запустит инициализацию класса и статический блок.
Чем блок инициализации отличается от конструктора?
Конструктор может принимать параметры и вызывается только тот, который выбран при new. Нестатический блок параметров не принимает и выполняется при любом конструкторе, перед его телом. Статический блок к объектам вообще не относится: он выполняется один раз при инициализации класса.
Может ли статический блок выполниться дважды?
В рамках одного загрузчика классов — нет, класс инициализируется один раз. Но если один и тот же класс загружен разными загрузчиками (например, в разных веб-приложениях одного сервера приложений), JVM считает их разными классами, и статический блок выполнится для каждого из них.
Потокобезопасен ли статический блок?
Сама инициализация — да: JVM блокирует класс на время выполнения static-блока, и другие потоки ждут его завершения, поэтому блок не выполнится параллельно и не выполнится дважды. Но если static-блок заполняет изменяемую коллекцию (например, HashMap), которую потом меняют из разных потоков, дальнейшие изменения нужно синхронизировать отдельно.
Video Explanation
Prefer video format? Watch this lesson with examples and explanations.
Comments