Главная » Java » Порядок выполнения конструкторов

0

В процессе создания объекта расширенного класса виртуальная машина выделяет память для хранения всех его полей, включая и те, которые унаследованы от базового класса, и последние получают исходные значения по умолчанию, отвечающие их типам (О (нуль) – для всех числовых типов, false – для boolean, ‘\uOOOO’ – для char и null – для ссылок на объекты). Далее процесс можно разделить на три следующие стадии:

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

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

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

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

 

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

 

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

 

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

 

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

 

class х {

 

            protected int xMask = 0x00ff;

            protected int fullMask;

            public x(){

 

            fullMask = xMask; 

            }

 

            public int mask(int orig) {

                        return (orig & fullMask); }

            }

 

            class у extends х {

                        protected int yMask =  0x00ff;

            public y() {

 

            fullMask |= yMask;

            }

}

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

 

Этап      Что происходит                                                                  xMask                               yMask           fullMask

 

о

Полям присвоены значения по умолчанию

0

0

0

1

Вызван конструктор Y

0

0

0

2

Вызван конструктор Х

0

0

0

3

Вызван конструктор Object

0

0

0

4

Проинициализированы поля Х

0x00ff

0

0

5

Выполнен конструктор Х

0x00ff

0

0x00ff

6

Проинициализированы поля Y

0x00ff

0xff00

0x00ff

7

Выполнен конструктор Y

0x00ff

0xff00

0xffff

 

 

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

Давайте, помимо того, представим, что в классе У метод mask подвергся переопределению – теперь он, скажем, реализован таким образом, что в нем для вычислений напрямую используется значение поля yMask. Если конструктор Х вызывает метод mask, он может на самом деле обратиться и к версии mask, объявленной в У, и в этот момент, разумеется, поле yMask будет содержать значение 0 вместо ожидаемого 0xff00.

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

Упражнение 3.2. Наберите приведенный выше текст классов Х и У и добавьте в него выражения, которые помогут про следить стадии изменения значений полей. Объявите метод main и выполните его, чтобы посмотреть на полученные результаты/

Упражнение 3.3. Если бы в процессе конструирования объекта оказалось сложно задавать значения числовых масок, используя поля расширенного класса, как вы преодолели бы подобные проблемы?

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии