Конструкторы Java - порядок выполнения в иерархии наследования

Рассмотрим приведенный ниже код

class Meal {
    Meal() { System.out.println("Meal()"); }
  }

  class Bread {
    Bread() { System.out.println("Bread()"); }
  }

  class Cheese {
    Cheese() { System.out.println("Cheese()"); }
  }

  class Lettuce {
    Lettuce() { System.out.println("Lettuce()"); }
  }

  class Lunch extends Meal {
    Lunch() { System.out.println("Lunch()"); }
  }

  class PortableLunch extends Lunch {
    PortableLunch() { System.out.println("PortableLunch()");}
  }

  class Sandwich extends PortableLunch {
    private Bread b = new Bread();
    private Cheese c = new Cheese();
    private Lettuce l = new Lettuce();
    public Sandwich() {
      System.out.println("Sandwich()");
    }
    public static void main(String[] args) {
      new Sandwich();
    }
  } 

Основываясь на моем понимании инициализации члена класса и порядке выполнения конструктора. Я ожидал, что вывод будет

Bread()
Cheese()
Lettuce()
Meal()
Lunch()
PortableLunch()    
Sandwich()

поскольку я считаю, что члены класса были инициализированы еще до вызова основного метода. Однако, когда я запускал программу

Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

моя путаница - это сыворотка() Lunch() и PortableLunch() перед Хлебом() Сыром() и Lettuce(), хотя их конструкторы, в которых вызывается.

Ответ 1

Это поля экземпляров

private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();

Они только существуют (выполняются), если экземпляр создан.

Первое, что работает в вашей программе, -

public static void main(String[] args) {
     new Sandwich();
}

Суперконструкторы называются неявно как первое в каждом конструкторе, т.е. до System.out.println

class Meal {
    Meal() { System.out.println("Meal()"); }
}

class Lunch extends Meal {
    Lunch() { System.out.println("Lunch()"); }
}

class PortableLunch extends Lunch {
    PortableLunch() { System.out.println("PortableLunch()");}
}

После вызовов super() экземпляры экземпляров создаются снова перед кодом конструктора.

Порядок, обратный

new Sandwich(); // prints last
// the instance fields
super(); // new PortableLunch() prints third
super(); // new Lunch() prints second
super(); // new Meal(); prints first

Ответ 2

даже если их конструкторы, вызываемые после.

Не после, вот как выглядит метод construstor для компилятора:

public Sandwich(){
    super();// note this calls super constructor, which will call it super and so on till Object constructor
    //initiate member variables
    System.out.println("Sandwich()");
}

Ответ 3

Я думаю, здесь есть две вещи, которые отбрасывают вас. Во-первых, main - статический метод, где в качестве переменных-членов b, c и l используются нестатические переменные экземпляра. Это означает, что они принадлежат объектам класса, а не самому классу. Поэтому, когда класс инициализирован для запуска основного метода, конструкторы Хлеба, Сыра и Салата не вызывают, так как не созданы экземпляры Sandwich.

Пока основной запуск не выполняется, а вызовы new Sandwich() - это объекты, которые фактически построены. В этот момент порядок операций:

  • инициализировать поля-члены базового класса
  • запустите конструктор базовых классов (классов)
  • инициализировать поля элемента этого класса
  • запустите конструктор этого класса

Это делается рекурсивно, поэтому в этом случае порядок будет

  • поля инициализации еды (нет)
  • запустить конструктор еды (печатает "Еда" )
  • поля инициализации Lunch (нет)
  • запустить конструктор Lunch (печатает "Lunch" )
  • поля инициализации PortableLunch (нет)
  • запустить конструктор PortableLunch (печатает "PortableLunch" )
  • поля инициализации сэндвича (отпечатки "Хлеб", "Сыр" и "Салат" )
  • запустить конструктор сэндвич (печатает "сэндвич" )

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

Ответ 4

Первый вызов в конструкторе всегда равен super(...). Этот вызов автоматически вставляется компилятором, если вы не записываете его явно. Никакие вызовы построенного объекта не могут произойти до возврата вызова super(). По завершении super() поля инициализируются в порядке появления, а затем выполняется остальная часть конструктора.

Ответ 5

private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();

Эти инициализаторы помещаются в конструктор Sandwich компилятором после супервызовного родительского класса.

Если они были статическими, то они произойдут первыми, потому что статические инициализаторы происходят при загрузке класса.