К. Арнольд, Д. Гослинг - Язык программирования Java (1160779), страница 30
Текст из файла (страница 30)
Каждый конструктор выполняется за трифазы: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 и проследить за его конструированием шаг за шагом, то значения полей будут меняться следующим образом:Шаг Что происходит0xMask yMask fullMaskПрисвоение полям значений по умолчанию0001Вызов конструктора Y0002Вызов конструктора X0003Инициализация полей X0x00ff 004Выполнение конструктора X0x00ff 00x00ff5Инициализация полей Y0x00ff 0xff00 0x00ff6Выполнение конструктора Y0x00ff 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: ExtendStrExtend.show: ExtendStrsup.str = SuperStrext.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 поддерживает обе возможности.3.4.1.
Ключевое слово superКлючевое слово super может использоваться во всех нестатических методах класса. При доступе к полям или вызове методов ключевое словоsuper представляет собой ссылку на текущий объект как экземпляр суперкласса. Использование super оказывается единственным случаем, прикотором выбор реализации метода зависит от типа ссылки. В вызове вида super.method всегда используется реализация method из суперкласса,а не его переопределенная реализация, которая находится где-то ниже в иерархии классов.Вызов методов с помощью ключевого слова super отличается от любых других ссылок, в которых метод выбирается в зависимости отфактического типа объекта, а не типа ссылки.
При вызове метода через ссылку super вы обращаетесь к реализации метода, основанной на типесуперкласса. Приведем пример практического использования super:class That {/** вернуть имя класса */protected String nm() {return “That”;}}class More extends That {protected String nm() {return “More”;}protected void printNM() {That sref = super;}System.out.println(“this.nm() = ” + this.nm());System.out.println(“sref.nm() = ” + sref.nm());System.out.println(“super.nm() = ” + super.nm());}А вот как выглядит результат работы printNM:this.nm() = Moresref.nm() = Moresuper.nm() = ThatКлючевое слово super может также применяться для доступа к защищенным членам суперкласса.3.5. Объявление методов и классов с ключевым словом finalЕсли метод объявлен с атрибутом final, это означает, что ни один расширенный класс не сможет переопределить данный метод с целью изменитьего поведение.