Порядок инициализации и инстанцирования Java

Я пытаюсь объединить процесс инициализации и инстанцирования в JVM, но JLS немного тупо на нескольких деталях, поэтому, если кто-то не возражает, чтобы прояснить некоторые детали, это будет оценено. Это то, что мне удалось выяснить до сих пор.

инициализация

  1. Рекурсивно Инициализировать статические конечные переменные класса и его интерфейсы, которые являются константами времени компиляции.

  2. Возврат из рекурсии обработки статических блоков и статических полей в текстовом порядке.

Конкретизация

  1. Рекурсивно Инициализировать конечные переменные экземпляра класса, которые являются константами времени компиляции.

  2. Возврат из рекурсии обработки нестатических блоков и полей экземпляра в текстовом порядке, доводя их до конструкторов по мере их возврата.


Хорошо, так что теперь для вопросов.

  1. обрабатываются интерфейсы в порядке декларации?

  2. обрабатываются интерфейсы в отдельном рекурсивном стеке?

    а) если да, интерфейсы обрабатываются до или после суперклассов?

    б) если да, могу ли я исправить вывод о том, что один или другие (Интерфейс или Суперкласс) получает свои константные поля не компиляции, инициализированные до остальных констант компиляции.

  3. Какую роль в этом процессе играют вызовы конструктора super() nefefault?

  4. Я ошибаюсь в любых моих выводах?

  5. Не хватает ли каких-либо других ключевых данных?

Ответ 1

Важно различать инициализацию класса и инициализацию объекта.

Инициализация класса

Класс или интерфейс инициализируется после первого доступа, назначая поля постоянной времени компиляции, а затем рекурсивно инициализируя суперкласс (если он еще не инициализирован), затем обработка статических инициализаторов (которые включают инициализаторы для статических полей, которые не являются константами времени компиляции).

Как вы заметили, инициализация класса сама по себе не инициирует инициализацию интерфейсов, которые она реализует. Поэтому интерфейсы инициализируются при первом обращении к ним, как правило, путем чтения поля, которое не является постоянной времени компиляции. Этот доступ может возникать при оценке инициализатора, вызывая рекурсивную инициализацию.

Также стоит отметить, что инициализация не запускается путем доступа к полям, которые являются константами времени компиляции, поскольку они оцениваются в время компиляции:

Ссылка на поле, которое является постоянной переменной (§4.12.4), должно быть разрешено во время компиляции до значения V, обозначенного инициализатором постоянной переменной.

Если такое поле является статическим, тогда никакая ссылка на поле не должна присутствовать в коде в двоичном файле, включая класс или интерфейс, которые объявили это поле. Такое поле всегда должно быть инициализировано (§12.4.2); начальное значение по умолчанию для поля (если оно отличается от V) никогда не должно наблюдаться.

Если такое поле не статично, то никакая ссылка на поле не должна присутствовать в коде двоичного файла, кроме класса, содержащего это поле. (Это будет класс, а не интерфейс, поскольку интерфейс имеет только статические поля.) Класс должен иметь код для установки значения поля V во время создания экземпляра (§12.5).

Инициализация объектов

Объект инициализируется всякий раз, когда создается новый объект, как правило, путем оценки выражения создания экземпляра класса. Это происходит следующим образом:

  • Назначьте аргументы конструктора новым созданным переменным параметра для этого вызова конструктора.

  • Если этот конструктор начинается с явного вызова конструктора (§8.8.7.1) другого конструктора в том же классе (с использованием этого), то затем оценивайте аргументы и обрабатывайте вызов конструктора рекурсивно, используя эти пять шагов. Если вызов конструктора завершается внезапно, то эта процедура завершается внезапно по той же причине; в противном случае перейдите к шагу 5.

  • Этот конструктор не начинается с явного вызова конструктора другого конструктора в том же классе (используя это). Если этот конструктор относится к классу, отличному от 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");
    }
}

Исполнение:

Просто вызовите основной метод, а затем проверьте вывод.

Советы:

  • Методы, отмеченные @PostConstruct, не будут вызываться, если вы не вызываете его внутри какого-либо контейнера, например Spring-boot, поскольку он зависит от этих контейнеров для реализации аннотации, например @PostConstruct.