Философия Java

         

Порядок вызова конструкторов


Порядок вызова конструкторов был кратко рассмотрен в главе 4 и снова в главе 6, но это было до того, как мы узнали о полиморфизме.

Конструктор для базового класса всегда вызывается в конструкторе дочернего класса, и так по всей цепочке наследования, пока не будут вызваны конструкторы всех базовых классов. Такой порядок имеет значение, поскольку конструктор выполняет специальную работу: что бы убедится, что объект был создан правильно. Дочерний класс имеет доступ только к его собственным членам и ни к одному из базового класса (чьи элементы обычно private). Только конструктор базового класса имеет необходимую информацию и доступ к элементам базового класса. Следовательно, естественно, что вызываются все конструкторы, с другой стороны объект целиком не создается. Вот поэтому компилятор и вызывает конструкторы в конструкторах дочерних классов. Он просто тихо вызывает конструктор по умолчанию, если Вы этого сами явно не сделали в теле конструктора. Если же у базового класса нет конструктора по умолчанию, то компилятор по этому поводу возразит. (В случае, если класс не имеет конструкторов компилятор автоматически создает конструктор по умолчанию.)

Давайте посмотрим на пример, который показывает эффект композиции, наследование и полиморфизма на стадии создания:

//: c07:Sandwich.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 { Bread b = new Bread(); Cheese c = new Cheese(); Lettuce l = new Lettuce(); Sandwich() { System.out.println("Sandwich()"); } public static void main(String[] args) { new Sandwich(); } } ///:~


Этот пример создает составной класс из других классов и каждый из классов имеет конструктор, который извещает о себе. Важный класс Sandwich отражает три уровня наследования (четыре, если считать наследование от Object) и три объекта элемента. Когда объект Sandwich уже создан, вывод программы таков:

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

Это означает, что существует следующий вызов конструкторов для сложного объекта:

  • Вызван конструктор базового объекта. Этот шаг был повторен пока вызов не добрался до корня иерархии, следуя вниз, до того, как будут обработаны все дочерние классы.
  • Участники инициализации вызваны по порядку их декларации.
  • Вызвано тело дочернего класса.


  • Порядок вызова конструкторов чрезвычайно важен. Когда Вы наследуете, Вы знаете все о базовом классе и можете получить доступ к любому public и protected его участнику. Это означает, что вам необходимо быть уверенным в том, что все члены класса приемлемы и допустимы на момент наследования. В нормальном методе, создание объекта уже завершено, поэтому все члены этого класса соответственно созданы. Внутри конструктора, однако, Вы должны быть уверены в том, что все участники класса созданы нормально. Существует только один путь, гарантирующий это - вызов конструктора базового класса в самую первую очередь. Затем, когда управление уже передается в конструктор дочернего класса, все участники базового класса будут проинициализированы и созданы должным образом. Знание того, что все члены класса приемлемы уже в конструкторе хорошая причина для того, что бы где только возможно инициализировать объекты на стадии их определения. Если Вы будете следовать этой практике, то Вы будете уверены, что все члены классов и члены объектов были правильно проинициализированы. Но, к сожалению, часто это не играет никакой роли, но об этом читайте в следующей секции.


    Содержание раздела