Порядок инициализации и создания экземпляра Java
Я пытаюсь собрать воедино процесс инициализации и создания экземпляра в JVM, но JLS немного запутан в некоторых деталях, поэтому, если кто-нибудь не возражает прояснить некоторые детали, это было бы оценено. Это то, что я смог выяснить до сих пор.
Инициализация
Рекурсивно инициализируйте статические конечные переменные класса и его интерфейсы, которые являются константами времени компиляции.
Откажитесь от рекурсии, обрабатывающей статические блоки и статические поля в текстовом порядке.
Создание экземпляра
Рекурсивно инициализируйте конечные переменные экземпляра класса, которые являются константами времени компиляции.
Отказывайтесь от рекурсии, обрабатывающей нестатические блоки и поля экземпляра в текстовом порядке, добавляя их к конструкторам по мере возврата.
Хорошо, теперь перейдем к вопросам.
обрабатываются ли интерфейсы в порядке объявления?
обрабатываются ли интерфейсы в отдельном рекурсивном стеке?
a) если да, обрабатываются ли интерфейсы до или после суперклассов?
b) если да, правильно ли я делаю вывод, что один или другие (интерфейс или суперкласс) инициализируют свои поля констант, не зависящих от времени компиляции, раньше других констант времени компиляции.
Какую роль в этом процессе играют вызовы конструктора super() по умолчанию?
Ошибаюсь ли я в каких-либо своих выводах?
Я упускаю какие-либо другие ключевые детали?
Переведено автоматически
Ответ 1
Важно различать инициализацию класса и инициализацию объекта.
Инициализация класса
Класс или интерфейс инициализируется при первом доступе путем назначения полей констант времени компиляции, затем рекурсивной инициализации суперкласса (если он еще не инициализирован), затем обработки статических инициализаторов (которые включают инициализаторы for для статических полей, которые не являются константами времени компиляции).
Как вы заметили, инициализация класса сама по себе не запускает инициализацию интерфейсов, которые он реализует. Таким образом, интерфейсы инициализируются при первом обращении к ним, обычно путем чтения поля, которое не является константой времени компиляции. Этот доступ может возникнуть во время вычисления инициализатора, вызывая рекурсивную инициализацию.
Также стоит отметить, что инициализация не запускается при доступе к полям, которые являются константами времени компиляции, поскольку они оцениваются во время компиляции:
Ссылка на поле, являющееся постоянной переменной (§4.12.4), должна быть преобразована во время компиляции в значение V, обозначаемое инициализатором постоянной переменной.
Если такое поле является статическим, то в коде двоичного файла не должно присутствовать никаких ссылок на это поле, включая класс или интерфейс, который объявил это поле. Такое поле всегда должно выглядеть инициализированным (§ 12.4.2); начальное значение по умолчанию для поля (если оно отличается от V) никогда не должно соблюдаться.
Если такое поле нестатическое, то в коде двоичного файла не должно присутствовать никаких ссылок на это поле, за исключением класса, содержащего это поле. (Это будет класс, а не интерфейс, поскольку интерфейс имеет только статические поля.) У класса должен быть код для установки значения поля в V при создании экземпляра (§12.5).
Инициализация объекта
Объект инициализируется всякий раз, когда создается новый объект, обычно путем вычисления выражения для создания экземпляра класса. Это выполняется следующим образом:
Присваивайте аргументы конструктора вновь созданным переменным параметров для этого вызова конструктора.
Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (используя this ), затем вычислите аргументы и обработайте этот вызов конструктора рекурсивно, используя те же пять шагов. Если вызов этого конструктора завершается внезапно, то и эта процедура завершается внезапно по той же причине; в противном случае продолжайте с шага 5.
Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (используя this ). Если этот конструктор предназначен для класса, отличного от Object , то этот конструктор будет начинаться с явного или неявного вызова конструктора суперкласса (с использованием super). Вычислите аргументы и обработайте рекурсивно вызов конструктора суперкласса, используя те же пять шагов. Если вызов этого конструктора завершается внезапно, то и эта процедура завершается внезапно по той же причине. В противном случае перейдите к шагу 4.
Выполните инициализаторы экземпляра и инициализаторы переменных экземпляра для этого класса, присвоив значения инициализаторов переменных экземпляра соответствующим переменным экземпляра в порядке слева направо, в котором они текстуально отображаются в исходном коде класса. Если выполнение любого из этих инициализаторов приводит к исключению, то дальнейшие инициализаторы не обрабатываются, и эта процедура внезапно завершается с тем же исключением. В противном случае продолжайте с шага 5.
Выполните остальную часть тела этого конструктора. Если это выполнение завершается внезапно, то и эта процедура завершается внезапно по той же причине. В противном случае эта процедура завершается нормально.
Как мы можем видеть на шаге 3, наличие явного вызова суперконструктора просто изменяет, какой конструктор суперкласса вызывается.
Ответ 2
Ниже приведен пример, который выводит порядок выполнения каждого шага во время создания объекта.
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, а затем проверьте результат.
Советы:
- The methods marked by
@PostConstruct
won't be invoked, unless you invoke it inside some container, likeSpring-boot
, since it depends on those containers to implement annotation like@PostConstruct
.