Срабатывает ли инициализация класса Java при любом обращении к его статическому полю?
Нет. Обращение к статическому полю обычно запускает инициализацию класса, но не запускает её, если поле является константной переменной: оно объявлено как static final и имеет примитивный тип или тип String, а его значение вычисляется константным выражением во время компиляции.
Статические поля предназначены для состояния, общего для всех экземпляров класса. Java отделяет загрузку класса от его инициализации, чтобы виртуальная машина могла подготовить метаданные класса заранее, но выполнять побочные эффекты статических инициализаторов только при реальном использовании.
Такой подход решает две задачи: не выполнять ненужную работу и гарантировать, что статическое состояние будет подготовлено до первого активного использования класса. Инициализация конкретного класса выполняется не более одного раза в рамках загрузчика классов.
Разработчик может считать, что любое чтение статического поля выполняет статический блок или вызывает статический метод, но это неверно для константных переменных. Компилятор способен встроить их значение непосредственно в использующий код, поэтому JVM не обязана инициализировать класс для такого чтения.
Ошибка особенно заметна при изменении значения константы в библиотеке: уже скомпилированное клиентское приложение может продолжить использовать старое встроенное значение до своей перекомпиляции. Поэтому публичные константы требуют осторожности при проектировании API.
Инициализация класса запускается перед первым активным использованием, например перед обращением к статическому методу, присваиванием статическому полю или чтением статического поля, если это поле не является константной переменной. Сначала инициализируются необходимые суперклассы, затем выполняются инициализаторы статических полей и статические блоки в порядке их появления в исходном тексте.
Константная переменная имеет специальное исключение. Для неё недостаточно только static final: значение должно быть допустимым константным выражением, которое компилятор может вычислить заранее. Ссылка на объект, вызов метода или значение, вычисляемое во время выполнения, этому условию не соответствует.
LIMIT — константная переменная примитивного типа, поэтому её значение может быть встроено в клиентский код. BOXED имеет ссылочный тип Integer, поэтому такое поле не является константной переменной в смысле Java, несмотря на static final; его чтение требует инициализации класса.
Инициализация потокобезопасна: если несколько потоков одновременно впервые используют класс, один поток выполняет инициализацию, а остальные ожидают её завершения. Если инициализация завершается ошибкой, класс переводится в ошибочное состояние, и последующие попытки активного использования обычно приводят к NoClassDefFoundError.
В библиотеке есть класс конфигурации с публичным значением тайм-аута. После выпуска новой версии значение изменили, но часть клиентов продолжила получать старое число. Причина — поле было константной переменной, и его значение оказалось встроено в байткод клиентских классов при компиляции.
Вариант с публичной константой удобен: обращение не требует инициализации класса и не создаёт дополнительных вычислений. Однако изменение значения требует перекомпиляции клиентов, что плохо подходит для настроек, меняющихся между версиями.
Вариант с методом-геттером или неконстантным статическим полем гарантирует получение актуального значения из библиотеки. Цена этого решения — обращение к классу действительно запускает его инициализацию, а значение может зависеть от порядка загрузки и внешних ресурсов.
Для конфигурации выбрали метод, возвращающий значение из централизованного источника. Это устранило встраивание значения в клиентский байткод и сделало изменение настройки предсказуемым без обязательной перекомпиляции клиентов.
static final, чтобы поле стало константой?Нет. Нужны одновременно допустимый тип — примитивный или String — и значение, вычисляемое как константное выражение. Поля типов-обёрток, массивов и произвольных объектов константными переменными не становятся.
Да, вызов статического метода является активным использованием класса и происходит после его инициализации. Если статический инициализатор завершится ошибкой, сам метод не будет вызван, а вызывающий код получит ошибку инициализации.
Загрузка класса и его инициализация — разные этапы. Сам факт загрузки, разрешения имени или наличия класса в classpath не гарантирует выполнения статических инициализаторов; они запускаются при активном использовании либо в других случаях, явно предусмотренных механизмом JVM.