Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
GoslingJava2.doc
Скачиваний:
139
Добавлен:
23.02.2016
Размер:
2.39 Mб
Скачать

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

При создании объекта всем его полям присваиваются исходные значения поумолчанию в зависимости от их типа (ноль для всех числовых типов, ‘\u0000’ для char, false для boolean и null для ссылок на объекты). Затем происходит вызов конструктора. Каждый конструктор выполняется за три фазы:

1. Вызов конструктора суперкласса.

2. Присвоение значений полям при помощи инициализаторов.

3. Выполнение тела конструктора.

Приведем пример, который позволит нам проследить за этой процедурой:

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;

}

}

Если создать объект типа Y и проследить за его конструированием шаг за шагом, то значения полей будут меняться следующим образом:

Шаг

Что происходит

xMask

yMask

fullMask

0

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

 

 

 

0

 

 

 

 

0

 

 

 

 

0

 

 

 

 

1

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

0

0

0

2

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

0

0

0

3

Инициализация полей X

0x00ff

0

0

4

Выполнение конструктора X

0x00ff

0

0x00ff

5

Инициализация полей Y

0x00ff

0xff00

0x00ff

6

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

0x00ff

0xff00

0xffff

Этот порядок имеет ряд важных следствий для вызова методов во время конструирования. Обращаясь к методу, вы всегда имеете дело с его реализацией для конкретного объекта; поля, используемые в нем, могут быть еще не инициализированы. Если на приведенном выше шаге4 конструктор X вызовет метод mask, то маска будет иметь значение 0x00ff, а не 0xffff, несмотря на то что в более позднем вызове mask (после завершения конструирования объекта) будет использовано значение 0xffff.

Кроме того, представьте себе ситуацию, в которой класс Y переопределяет реализацию mask так, чтобы в вычислениях явно использовалось поле yMask. Если конструктор X использует метод mask, на самом деле будет вызван mask класса Y, а в этот момент значение yMask равно нулю вместо ожидаемого 0xff00.

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

Упражнение 3.2

Наберите код приведенных выше классов X и Y и включите в него операторы вывода для наблюдения за значениями масок. Напишите метод main и запустите его, чтобы ознакомиться с результатами. Переопределите mask в классе Y и снова выполните тестирование.

Упражнение 3.3

Если правильные значения масок оказываются абсолютно необходимыми для конструирования, какой бы выход вы предложили?

3.4. Переопределение методов и скрытие полей

В своем новом классе ColorAttr мы переопределили и перегрузили метод valueOf, устанавливающий значение атрибута:

  • Перегрузка (overloading) метода рассматривалась нами раньше; под этим термином понимается создание нескольких методов с одинаковыми именами, но с различными сигнатурами, по которым эти методы отличаются друг от друга.

  • Переопределение (overriding) метода означает, что реализация метода, взятая из суперкласса, заменяется вашей собственной. Сигнатуры методов при этом должны быть идентичными. Обратите внимание: переопределению подлежат только нестатические методы.

В классе ColorAttr мы переопределили метод Attr.valueOf(Object), создав новый метод ColorAttr.valueOf(Object). Этот метод сначала обращается к реализации суперкласса с помощью ключевого слова super и затем вызывает метод decodeColor. Ссылка super может использоваться для вызова методов суперкласса, переопределяемых в данном классе. Позднее мы подробно рассмотрим ссылку super.

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

Переопределенные методы могут иметь собственные значения атрибутов доступа. Расширенный класс может изменить права доступа к методам, унаследованным из суперкласса, но лишь в том случае, если он расширяет их. Метод, объявленный в суперклассе как protected, может быть повторно заявлен как protected (вполне обычная ситуация) или public, но не как private. Ограничивать доступ к методам по сравнению с суперклассом на самом деле было бы бессмысленно, поскольку такое ограничение очень легко обойти: достаточно преобразовать ссылку в супертип с большими правами доступа и использовать ее для вызова метода.

Поля не могут переопределяться; вы можете лишь скрыть их. Если объявить в своем классе поле с тем же именем, что и в суперклассе, то поле суперкласса никуда не исчезнет, однако к нему уже нельзя будет обратиться напрямую, используя одно имя. Для доступа к такому полю нужно будет воспользоваться super или другой ссылкой на тип суперкласса.

При вызове метода для некоторого объекта его реализация выбирается в зависимости от фактического типа объекта. При доступе к полю используется объявленный тип ссылки. Разобраться в этом поможет следующий пример:

class SuperShow {

public String str = “SuperStr”;

public void show() {

System.out.println(“Super.show: ” + str);

}

}

class ExtendShow extends SuperShow {

public String str = “ExtendStr”;

public void show() {

System.out.println(“Extend.show: ” + str);

}

public static void main(String[] args) {

ExtendShow ext = new ExtendShow();

SuperShow sup = ext;

sup.show();

ext.show();

System.out.println(“sup.str = ” + sup.str);

System.out.println(“ext.str = ” + ext.str);

}

}

У нас имеется всего один объект, но на него указывают две ссылки— тип одной из них совпадает с типом объекта, а другая объявлена как ссылка на суперкласс. Вот как выглядят результаты работы данного примера:

Extend.show: ExtendStr

Extend.show: ExtendStr

sup.str = SuperStr

ext.str = ExtendStr

Метод show ведет себя именно так, как следовало ожидать: вызываемый метод зависит от фактического типа объекта, а не от типа ссылки. Когда мы имеем дело с объектом ExtendShow, вызывается метод именно этого класса, даже если доступ к нему осуществляется через ссылку на объект типа SuperShow.

Что касается поля str, то выбор класса, которому принадлежит это поле, осуществляется на основании объявленного типа ссылки, а не фактического типа объекта. В сущности, каждый объект класса ExtendShow содержит два поля типа String, каждое из которых называется str; одно из них наследуется от суперкласса и скрывается другим, собственным полем класса Extend Show:

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

Если существующий метод получает параметр типа SuperShow и обращается к str через ссылку на объект-параметр, он всегда будет получать Super Show.str, даже если методу на самом деле был передан объект типа Extend Show. Если бы классы были спроектированы так, чтобы для доступа к строке применялся специальный метод, то в этом случае был бы вызван переопределенный метод, возвращающий ExtendShow.str. Это еще одна из причин, по которой определение классов с закрытыми данными, доступ к которым осуществляется с помощью методов, оказывается предпочтительным.

Скрытие полей разрешено в Java для того, чтобы при новой реализации существующих суперклассов можно было включить в них новые поля с атрибутами public или protected, не нарушая при этом работы подкласса. Если бы использование одинаковых имен в суперклассе и подклассе было запрещено, то включение нового поля в существующий суперкласс потенциально могло бы привести к конфликтам с подклассами, в которых это имя уже используется. В таком случае запрет на добавление новых полей к существующим суперклассам связывал бы руки программистам, которые не могли бы дополнить суперклассы новыми полями с атрибутами public или protected. Формально можно было бы возразить, что классы должны содержать только данные private, но Java поддерживает обе возможности.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]