В процессе создания объекта расширенного класса виртуальная машина выделяет память для хранения всех его полей, включая и те, которые унаследованы от базового класса, и последние получают исходные значения по умолчанию, отвечающие их типам. Далее процесс можно разделить на следующие стадии:
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, просмотров: 259.