Java порядок инициализации и создания экземпляров
Я пытаюсь собрать воедино процесс инициализации и создания экземпляров в JVM, но JLS немного туповат в некоторых деталях, поэтому, если кто-то возражает прояснить некоторые детали, это будет оценено. Это то, что я смог понять до сих пор.
инициализация
Рекурсивно Инициализируйте статические конечные переменные класса и его интерфейсов, которые являются константами времени компиляции.
Возврат из рекурсии обработки статических блоков и статических полей в текстовом порядке.
Конкретизация
Рекурсивно Инициализируйте конечные переменные экземпляра класса, которые являются константами времени компиляции.
Откажитесь от обработки рекурсии нестатических блоков и полей экземпляра в текстовом порядке, добавляя их к конструкторам по мере их возврата.
Хорошо, теперь вопросы.
интерфейсы обрабатываются в порядке объявления?
интерфейсы обрабатываются в отдельном рекурсивном стеке?
а) если да, обрабатываются ли интерфейсы до или после суперкласса?
б) если да, правильно ли я могу сделать вывод, что один или другие (интерфейс или суперкласс) получают свои поля констант не во время компиляции, инициализированные до других констант во время компиляции.
Какую роль в этом процессе играют вызовы конструктора не по умолчанию super()?
Я ошибаюсь в каком-либо из моих выводов?
Я упускаю какие-либо другие ключевые детали?
2 ответа
Важно различать инициализацию класса и инициализацию объекта.
Инициализация класса
Класс или интерфейс инициализируются при первом доступе путем назначения полей констант времени компиляции, затем рекурсивной инициализации суперкласса (если он еще не инициализирован), а затем обработки статических инициализаторов (которые включают инициализаторы для статических полей, которые не являются временем компиляции). константы).
Как вы заметили, инициализация класса сама по себе не запускает инициализацию интерфейсов, которые он реализует. Поэтому интерфейсы инициализируются при первом обращении к ним, обычно путем чтения поля, которое не является постоянной времени компиляции. Этот доступ может происходить во время оценки инициализатора, вызывая рекурсивную инициализацию.
Стоит также отметить, что инициализация не инициируется путем доступа к полям, которые являются константами времени компиляции, так как они оцениваются во время компиляции:
Ссылка на поле, которое является постоянной переменной (§4.12.4), должна быть преобразована во время компиляции в значение V, обозначаемое инициализатором постоянной переменной.
Если такое поле является статическим, то в коде не должно быть ссылки на поле в двоичном файле, включая класс или интерфейс, который объявил поле. Такое поле всегда должно казаться инициализированным (§12.4.2); начальное значение по умолчанию для поля (если оно отличается от V) никогда не должно соблюдаться.
Если такое поле нестатично, то в коде двоичного файла не должно быть ссылки на поле, кроме класса, содержащего это поле. (Это будет класс, а не интерфейс, так как интерфейс имеет только статические поля.) У класса должен быть код для установки значения поля в V во время создания экземпляра (§12.5).
Инициализация объекта
Объект инициализируется всякий раз, когда создается новый объект, обычно путем вычисления выражения создания экземпляра класса. Это происходит следующим образом:
Присвойте аргументы конструктора вновь созданным переменным параметра для этого вызова конструктора.
Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (с использованием этого), то оцените аргументы и обработайте этот вызов конструктора рекурсивно, используя эти же пять шагов. Если этот вызов конструктора завершается внезапно, то эта процедура завершается преждевременно по той же причине; в противном случае перейдите к шагу 5.
Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (используя это). Если этот конструктор предназначен для класса, отличного от Object, тогда этот конструктор начнется с явного или неявного вызова конструктора суперкласса (с использованием super). Оцените аргументы и обработайте этот вызов конструктора суперкласса рекурсивно, используя те же пять шагов. Если этот вызов конструктора завершается преждевременно, то эта процедура завершается преждевременно по той же причине. В противном случае перейдите к шагу 4.
Выполните инициализаторы экземпляров и инициализаторы переменных экземпляра для этого класса, присваивая значения инициализаторов переменных экземпляра соответствующим переменным экземпляра, в порядке слева направо, в котором они появляются текстово в исходном коде для класса. Если выполнение какого-либо из этих инициализаторов приводит к исключению, то дальнейшие инициализаторы не обрабатываются, и эта процедура резко завершается с тем же исключением. В противном случае перейдите к шагу 5.
Выполните остальную часть тела этого конструктора. Если это выполнение завершается внезапно, то эта процедура завершается преждевременно по той же причине. В противном случае эта процедура завершается нормально.
Как мы видим на шаге 3, наличие явного вызова супер-конструктора просто меняет, какой конструктор суперкласса вызывается.
Ниже приведен пример, который печатает порядок каждого шага во время создания объекта.
InstanceCreateStepTest.java:
import javax.annotation.PostConstruct;
/**
* Test steps of instance creation.
*
* @author eric
* @date Jan 7, 2018 3:31:12 AM
*/
public class InstanceCreateStepTest {
public static void main(String[] args) {
new Sub().hello();
System.out.printf("%s\n", "------------");
new Sub().hello();
}
}
class Base {
static {
System.out.printf("%s - %s - %s\n", "base", "static", "block");
}
{
System.out.printf("%s - %s - %s\n", "base", "instance", "block");
}
public Base() {
System.out.printf("%s - %s\n", "base", "constructor");
}
@PostConstruct
public void init() {
System.out.printf("%s - %s\n", "base", "PostConstruct");
}
public void hello() {
System.out.printf("%s - %s\n", "base", "method");
}
}
class Sub extends Base {
static {
System.out.printf("%s - %s - %s\n", "sub", "static", "block");
}
{
System.out.printf("%s - %s - %s\n", "sub", "instance", "block");
}
public Sub() {
System.out.printf("%s - %s\n", "sub", "constructor");
}
@PostConstruct
public void init() {
System.out.printf("%s - %s\n", "sub", "PostConstruct");
}
@Override
public void hello() {
// super.hello();
System.out.printf("%s - %s\n", "sub", "method");
}
}
Исполнение:
Просто вызовите метод main, а затем проверьте вывод.
Подсказки:
- Методы, отмеченные
@PostConstruct
не будет вызван, если вы не вызовете его внутри некоторого контейнера, какSpring-boot
, поскольку это зависит от этих контейнеров для реализации аннотации, как@PostConstruct
,