Давайте попробуем по-другому объяснить это ...
Это последовательность, которую JVM выполняет, когда вы впервые ссылаетесь на класс MyClass
.
- Загрузить байт-код в память.
- Память для статического хранилища очищена (двоичный ноль).
- Инициализировать класс:
- Выполните каждый статический инициализатор в том порядке, в котором он появляется, включая статические переменные и
static { ... }
блоки.
- JVM затем инициализирует вашу
myClass
статическую переменную для нового экземпляра MyClass
.
- Когда это происходит, JVM замечает, что
MyClass
уже загружен (байт-код) и находится в процессе инициализации , поэтому пропускает инициализацию.
- Выделить память в куче для объекта.
- Выполнить конструктор.
- Выведите значение
obj
, которое все еще равно null
(поскольку оно не является частью инициализированных переменных кучи и конструктора).
- Когда конструктор завершит работу, выполните следующий статический инициализатор, который устанавливает
obj
на новый экземпляр Object
.
- Инициализация класса выполнена. С этого момента все вызовы конструктора будут вести себя так, как вы предполагаете / ожидаете, то есть
obj
будет не null
, а ссылкой на экземпляр Object
.
Помните, что Java указывает, что переменной final
присваивается значение один раз. Дело не в том, что гарантированно будет присвоено значение, когда код ссылается на него, если только вы не убедитесь, что код ссылается на него после того, как оно назначено.
Это не ошибка. Это определенный способ обработки использования класса во время его собственной инициализации. Если бы это было не так, то JVM пошла бы в бесконечный цикл. См. Шаг № 3.3 (если JVM не пропускает инициализацию для класса, который находится в процессе инициализации, он просто продолжит его инициализацию - бесконечный цикл).
Обратите внимание, что все это происходит в том же потоке, который сначала ссылается на класс. Во-вторых, JVM гарантирует, что инициализация будет завершена до того, как любой другой поток сможет использовать этот класс.