Порядок выполнения конструкторов
Поможем в ✍️ написании учебной работы
Поможем с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой

В процессе создания объекта расширенного класса виртуальная машина выделяет память для хранения всех его полей, включая и те, которые унаследованы от базового класса, и последние получают исходные значения по умолчанию, отвечающие их типам. Далее процесс можно разделить на следующие стадии:

1. вызов конструктора базового класса;

2. присваивание исходных значений полям объекта посредством выполнения соответствующих выражений и блоков инициализации;

3. выполнение инструкций, предусмотренных в теле конструктора.

В первую очередь выполняется явное или косвенное обращение к конструктору базового класса. Если осуществляется явный вызов конструктора того же производного класса с помощью ссылки this, подобная цепочка вызовов конструкторов этого класса выполняется до тех пор, пока не будет найдена инструкция явного или косвенного обращения к конструктору базового класса, после чего таковой и вызывается. Конструктор базового класса выполняется в соответствии с той же последовательностью стадий – процесс продолжается рекурсивно, завершаясь по достижении конструктора класса Object, поскольку далее не существует конструкторов классов более высокого уровня иерархии. В выражениях, выполняемых в процессе вызова конструктора базового класса, не допускается присутствие ссылок на любые члены текущего объекта.

На второй стадии процесса обрабатываются выражения и блоки инициализации полей в порядке их объявления. В этот момент ссылки на другие члены объекта допустимы, если таковые уже полностью определены.

Наконец, выполняются выражения тела конструктора. Если текущий конструктор был вызван явно, по его завершении управление передаётся в тело конструктора-инициатора, где выполняются оставшиеся инструкции. Процесс повторяется до тех пор, пока управление не будет передано обратно в тело конструктора «исходного» производного класса, название которого было указано в выражении new.

Если во время процесса конструирования выбрасывается исключение, виртуальная машина завершает выполнение выражения new, генерируя то же исключение, и не возвращает ожидаемую ссылку на объект. Поскольку выражение явного вызова конструктора текущего или базового класса должно быть первым в теле конструктора-инициатора, отловить исключение, выброшенное вызванным конструктором, невозможно. (если бы язык допускал подобное, существовала бы и вероятность создания объектов с неверным исходным состоянием.)

Пример 24 иллюстрирует различные стадии процесса конструирования объекта производного класса.

Пример 24.

class X {

protected int xMask = 0x00ff;

protected int fullMask;

public X() {

      fullMask = xMask;

}

public int mask (int orig) {

      return (orig & fullMask);

}

}

class Y extends X {

protected int yMask = 0xff00;

public Y() {

      fullMask |= yMask ;

}

}

Ниже в таблице 16 приведены сведения о содержимом полей, объявленных в классах X и Y, после выполнения каждого этапа процесса создания объекта класса Y.

Таблица 16

Этап Что происходит xMask yMask fullMask
0 Полям присвоены значения по умолчанию 0 0 0
1 Вызван конструктор Y 0 0 0
2 Вызван конструктор X 0 0 0
3 Вызван конструктор Object 0 0 0
4 Проинициализированы поля X 0x00ff 0 0
5 Выполнен конструктор X 0x00ff 0 0x00ff
6 Проинициализированы поля Y 0x00ff 0xff00 0x00ff
7 Выполнен конструктор Y 0x00ff 0xff00 0xffff

Если в ходе конструирования объекта вызываются методы, важно представлять себе порядок операций и понимать, что происходит на каждом этапе процесса. В подобной ситуации при вызове метода мы всегда имеем дело с версией этого метода для фактического типа объекта; наличие в полях объекта предусмотренных исходных данных в этот момент не гарантируется. Так, например, если на шаге 5 конструктор X вызывал бы метод mask , использовалось бы текущее значение поля fullMask , равное 0 x 00 ff , но никак не 0 xffff . И это совершенно справедливо, хотя тот же метод mask , будучи вызванным позже, после завершения процесса конструирования, получил бы значение fullMask , равное 0 xffff .

Давайте, помимо того, представим, что в классе Y метод mask подвергся переопределению:  теперь он реализован таким образом, что в нём для вычислений напрямую используется значение поля yMask . Если конструктор X вызывает метод mask , то он может на самом деле обратиться и к версии mask , объявленной в Y , и в это время, разумеется, поле YMask будет содержать значение 0 вместо ожидаемого 0 xff 00.

Методы, предназначенные для вызова на стадии конструирования объекта, должны быть спроектированы особенно тщательно, с учётом вышеназванных соображений. В конструкторах следует избегать вызовов методов, допускающих переопределение, т.е. всех тех, которые не помечены как private, static или final.

Дата: 2019-02-19, просмотров: 217.