Будь умным!


У вас вопросы?
У нас ответы:) SamZan.net

тематики Калифорнийского университета в Беркли классы и интерфейсы могут быть объявлены в составе други

Работа добавлена на сайт samzan.net: 2016-06-20

Поможем написать учебную работу

Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.

Предоплата всего

от 25%

Подписываем

договор

Выберите тип работы:

Скидка 25% при заказе до 18.5.2024

ГЛАВА

5

ГЛАВА

5

          ГЛАВА 5

   Вложенные классы и интерфейсы

  "Любое расширение группы А посредством группы В изоморфно вкладывается в декартово сплетение А - В".

Теорема имеет смысл только в том случае, если вы не будете о ней думать.

Из высказываний одного профессора математики Калифорнийского университета в Беркли

классы и интерфейсы могут быть объявлены в составе других классов и интерфейсов, как в качестве членов, так и внутри блоков кода. Такие вложенные классы (nested classes) и вложенные интерфейсы (nested interfaces) способны принимать различные формы, каждая из которых обладает собственными свойствами и назначением.

Возможность определять вложенные типы, предусмотренная в языке Java, служит нескольким целям. Во-первых, вложенные классы и вложенные интерфейсы позволяют представить тип в виде логически связанных структурных групп и контекстов. Во-вторых - и это самое важное, - вложенные классы могут быть использованы как простой и эффективный инструмент объединения семантически соотносимых объектов. Эта возможность активно применяется при создании программных сред, управляемых событиями, примерами могут служить пакет АWT (Abstract Window Toolkit) (обратитесь к разделу 20.1 на странице 547), и компонентная архитектура JavaBeans™ (описанная в разделе 20.3 на странице 551).

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

5.1. Статические вложенные типы

Вложенный класс или интерфейс, объявленный в виде статического (static) Члена внешнего класса или интерфейса, действует так же, как любой обычный (не вложенный) класс или интерфейс, за тем лишь исключением, что его имя и Свойства доступности определяются внешним типом. Имя вложенного типа зада-

ется в форме ИмяВнешнегоТипа. ИмяВложенногоТипа. Вложенный тип доступен только в том случае, если доступен соответствующий внешний тип.

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

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

5.1.1. Статические вложенные классы

Статический вложенный класс - это простейшая форма вложенного класса; в его объявлении присутствует служебное слово static. Если Класс вложен в интерфейс, он получает статус статического по умолчанию, и Модификатор statiс, в соответствии с приняты м соглашением, опускается. Статический вложенный класс ведет себя в точности так же, как и заурядный класс "верхнего" уровня. Он способен наследовать другие классы, реализовывать любые Интерфейсы и сам по себе служить объектом расширения для любого Класса, обладающего необходимыми правами доступа. В объявлении статического вложенного класса, как и обычного класса, разрешается применять модификаторы final и abstract

Статические вложенные классы служат в качестве механизма определения логически связанных типов внутри контекста, в котором эти типы имеют смысл. Например, в разделе 2.6.3 на странице 75 мы продемонстрировали Класс Permissions, предназначенный для хранения информации об объекте класса BankAccount, представляющего банковский счет. поскольку Класс Permissions связан с BankAccount Класса BankAccount - объект типа Permissions подтверждает права владельца банковского счета на выполнение тех или иных операций, - его можно считать удачным кандидатом на роль вложенного класса:

  1.                                                                                       ГЛАВА 5. ВЛОЖЕННЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Класс Permissions определен внутри объявления класса BankAccount и является членом последнего. Когда метод Permissions класса BankAccount возвращает объект типа Permissions, метод может обращаться к классу просто по Permissions - точно так же, как к полям number и balanсе, без дополнительных уточнений: не будем забывать, что Permissions - это полноправный член класса. имя вложенного Класса Permissions выглядит как BankAccount. Permissions и ясно свидетельствует, что этот класс является частью класса BankAccount, а не самостоятельным типом. Код за пределами класса BankAccount обязан обращаться к вложенному типу с указанием его полного имени

BankAccount. Permissions реrm = acct. PermissionsFor(owner);

Если бы класс BankAccount размещался в пакете с именем bank, полным наименованием вложенного класса было бы bank. BankACcount. Регт; 55; ОП5 (более полные сведения о пакетах приведены в главе 13). Решая прикладную задачу, можно импортировать класс BankAccount. Permissions и далее обращаться к нему, употребляя простое имя Permissions, но в таком случае важная информация о "подчиненном" характере класса будет утрачена.

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

Поскольку Permissions - член класса BankAccount, он способен обращаться ко всем другим членам BankAccount, включая и унаследованные. Если бы, например, в составе Permissions был объявлен метод, предусматривающий прием объекта BankAccount в виде аргумента, такой метод обладал бы правом непосредственного доступа к полям number и bаlаnсе объекта BankAccount. В этом смысле вложенный класс выглядит как часть реализации внешнего класса и поэтому заслуживает полного доверия

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

5.1.2. Вложенные интерфейсы

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

5.1. СТАТИЧЕСКИЕ ВЛОЖЕННЫЕ ТИПЫ         141

объект. Строку метода deposit, в которой создается объект Action, можно бы]10 бы переписать в равноценной, но более строгой форме

lastAct = this.new Асtion("приход", amount);

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

Обратите внимание, как создаваемый объект Action связывается с объектом BankAccount, на который указывает параметр other

Правила объявления внутренних классов аналогичны тем, которые приняты для традиционных классов, за исключением того, что внутренние классы не способны содержать статические члены, кроме полей вида f; nal stat; с, инициализируемых константами или постоянными выражениями. Доводы в пользу возможности объявления констант в составе внутренних классов те же, что и в случае интерфейсов, - классу удобно пользоваться собственными постоянными величинами.

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

5.2.1. Доступ к внешним объектам

Вы, вероятно, обратили внимание, что метод toString класса Action обращается к полю number внешнего класса BankAccount непосредственно. Вложенные классы обладают полными правами доступа ко всем членам внешнего класса - включая методы и поля private, - без необходимости дополнительных разъяснений, поскольку сами являются членами этого класса. Чтобы обратиться к члену внешнего класса, в коде внутреннего класса достаточно задать только Имя этого члена. Имена членов внешнего класса, как принято говорить, находятся в контексте этого класса. Внешний класс, в свою очередь, способен обращаться к приватным членам своего внутреннего класса, но только посредством явной ссылки на соответствующий объект внутреннего класса - такой как LastAct. Хотя объект внутреннего класса всегда ассоциирован с объектом Внешнего класса, обратное утверждение, строго говоря, не верно. Объект внешнего класса не обязательно должен обладать ссылками на объекты собственного внутреннего класса - таких ссылок, возможно, не будет вовсе или их может быть несколько.

Когда в теле метода deposit создается объект Action, в последнем автоматически cохраняется ссылка на текущий внешний объект класса BankAccount. Пользу-

5.2. ВНУТРЕННИЕ КЛАССЫ           143

ясь этой неявной ссылкой, объект Action позже всегда может обратиться, скажем, к полю пате "родного" объекта BankAccount просто по имени, как показано в тексте метода toString. Полная ссылка на текущий объект внешнего класса выглядит как слово this, которому предшествуют строка имени класса и оператор точки. Так, например, метод toString вправе адресовать поле number объекта внешнего класса BankAccount, задавая выражение полной ссылки:

return BankAccount.this.number + ": " + act + " " + amount; Подобная конструкция позволяет еще раз подчеркнуть, что объекты внутреннего и внешнего классов тесно связаны в рамках реализации внешнего класса.

Вложенный класс способен содержать собственные вложенные классы и интерфейсы. Правила формирования ссылок на внешние объекты при любом уровне вложенности классов одинаковы: необходимо задать имя класса и ссылку this. Так, например, если в классе Х содержится класс У, в котором, в свою очередь, содержится класс Z, то код класса Z может явно обращаться к членам класса Х, пользуясь ссылкой

Х. this.

Допустимый уровень вложенности классов и интерфейсов формально не ограничен - подобные вопросы относятся, в большей мере, к сфере здравого смысла, хорошего вкуса и стиля программирования. Класс, такой как Z, дважды "упрятанный" в другие классы, имеет дело с тремя пространствами имен - собственным, а также пространствами имен непосредственного "начальника" У и "руководителя верхнего звена" Х. Читатель, разбирающий код класса Z, должен быть хорошо осведомлен о структуре всех трех классов, чтобы отчетливо понимать, какому контексту принадлежит каждый встреченный в тексте идентификатор и на какие объекты внешних классов указывают ссылки из объектов внутренних классов. Мы рекомендуем в большинстве ситуаций не применять классы более одного уровня вложенности. В противном случае проблемы, связанные с не возможностью восприятия и практического использования кода, не заставят себя ждать.

5.2.2. Расширение внутренних классов

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

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

144       ГЛАВА 5. ВЛОЖЕННЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

неявно обращается к конструктору по умолчанию класса Inner. Конструктор Inner требует наличия объекта Outer, к которому следует "привязаться", - в этой роли выступает текущий объект класса ExtendedOuter.

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

class Unrеlated extends Outer.Inner { Unrеlated(outer ref) {

ref. super() ;

}

}

Когда процесс конструирования объекта класса Unrеlated достигает точки вызова конструктора базового класса, должна существовать ссылка на объект класса outeг, к которому можно было бы привязать объект базового класса. Unrеlated сам по себе не является внутренним классом класса Outer, поэтому в данном случае неявный внешний объект отсутствует. Аналогично, поскольку Unrеlated не относится к числу классов, производных от Outer, текущий объекТ Unrеlated не будет правильным внешним объектом. Нам надлежит обеспечить явную ссылку на объект Outer, к которому удастся привязать объект базового класса. Мы остановили выбор на способе передачи ссылки в виде аргумента конструктора класса Unrеlated. Этот конструктор использует ее как явную связывающую ссылку при вызове конструктора базового класса.

Обратите внимание на то, что нельзя пользоваться синтаксисом построения объекта внутреннего класса для создания требуемого объекта Outer "внешним образом":

Outer ref = new Outer();

Unrеlated u = ref.new Unrеlated(); // НЕВЕРНО!

"Указанный код предполагает создание внешнего объекта для класса Unrеlated, но Unrеlated не является внутренним классом.

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

5.2.3. Наследование, контекст и сокрытие

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

145          5.2. ВНУТРЕННИЕ КЛАССЫ

 

поле или метод объявлены во внутреннем классе

поле или метод унаследованы внутренним классом

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

Во втором случае использование простых имен не разрешается. Имя должно быть снабжено дополнительной ссылкой this либо super, если оно указывает на член текущего или базового внутреннего класса, либо квалификатором ИмяВнешнегоКласса.this, если ссылается на член внешнего класса. Причина введения такого требования связана с попыткой предотвратить возникновение Ситуаций, когда код, который, как кажется, должен делать одно, в действительности выполняет совершенно другое. Рассмотрим следующий фрагмент кода:

 

class Host{

int x;

class Helper extends Unknown {

 void increment() {x++;}

 // неверно!

}

}

Метод increment должен, как предусмотрено, увеличить значение переменной Х в экземпляре внешнего класса Host. Объявление поля х, предположим, присутствует и в составе класса unknown и наследуется классом Неlреr. Унаследованное поле Х скрывает одноименное поле, принадлежащее контексту внешнего класса, поэтому, если бы приведенный выше код был допустим, он на самом деле означал бы увеличение на единицу значения поля х, унаследованного от unknown, и у читателя сложилось бы превратное впечатление о том, что произошло в действительности. Чтобы исключить подобные проблемы, компилятор требует снабдить переменную х явным квалификатором, который в состоянии ответить на вопрос, какое из двух полей х имеется в виду - this.х (поле текущего объекта) или Host.this.х (поле объекта внешнего класса).

Метод внутреннего класса перекрывает все перегруженные версии одноименного метода внешнего класса - даже в том случае, если в составе самого внутреннего класса нет соответствующих перегруженных вариантов метода. Например:

class Outer {

 void print(){}

 void print(int val) {}

 class Inner {

  void print(){}

  void show() {

   print();

   Outer.this.print();

   print(1); // ошибка: нет метода inner.print(int)

  }

 }

}

Объявление Inner.рrint перекрывает все версии Outer.рrint. Когда в Inner.show вызывается print(l), компилятор фиксирует, что в составе класса

146      ГЛАВА 5. ВЛОЖЕННЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Inner нет объявления метода рrint с целочисленным параметром. Чтобы обратиться к варианту метода, объявленному во внешнем классе, следует явно задать квалификатор outer.this. Как обычно, от подобного сокрытия полей и методов проблем гораздо больше, нежели пользы.

Упражнение 5.1. Создайте версию класса BankAccount, в которой предусматривается возможность сохранения информации о десяти последних операциях, Выполненных с банковским счетом. Добавьте метод history, возвращающий объект класса History, метод next которого позволяет получать в хронологическом Порядке объекты Action с соответствующими данными об операциях и возвращает null, если список выполненных операций исчерпан. Следует ли объявить History в Виде вложенного класса? Если да, то какого - статического или нет?

5.3. Локальные внутренние классы

Разрешается объявлять вложенные классы внутри блоков кода, таких как тело метода, конструктор или блок инициализации. Подобный локальный внутренний класс (local inner class) не является членом класса, к которому относится блок, а принадлежит самому блоку - точно так же, как обычная локальная переменная. Такие классы совершенно недоступны за пределами внешнего класса, поскольку нет никаких способов обращения к ним, но их экземпляры - это обычные объекты, которые позволяется, например, передавать в качестве аргументов или возвращать из методов. Объект локального внутреннего класса "живет" до тех пор, пока существует хотя бы одна ссылка на него. Единственный модификатор, который допускается применять в объявлении локального класса, - это final, предотвращающий, как обычно, возможность расширения класса.

Код локального внутреннего класса обладает правом доступа ко всем переменным, принадлежащим контексту того же блока, - локальным переменным, параметрам метода, переменным экземпляра (в предположении, что блок не статический) и статическим переменным. Единственное ограничение состоит в том, что локальная переменная или параметр метода становятся доступными, если только они помечены признаком final. Причина введения подобного ограничения обусловлена большей частью проблемами поддержки многопоточных приложений (подробная информация приведена в главе 10), когда необходимо гарантировать, что все переменные, на которые "покушается" код соседнего локального внутреннего класса, инициализированы устойчивыми предсказуемыми значениями. Если необходимо, вы вправе загодя скопировать значение переменной нe-final в соответствующее поле final - до того момента, когда к нему Сможет обратиться код локального внутреннего класса.

Рассмотрим стандартный интерфейс Iterator, объявленный в пакете java.util. Интерфейс определяет способ просмотра группы объектов. Обычно он используется для упорядочения доступа к элементам контейнерного объекта, Но может быть применен также и для поддержки любого процесса итерации:

package java.util;

public interface Iterator {

boo1ean hasNext();

Object next() throws NosuchElementException;

void remove() throws NosuchElementException;

}

5.3. ЛОКАЛЬНЫЕ ВНУТРЕННИЕ КЛАССЫ        147

Метод hasNext возвращает значение true, если существуют объекты, которые могут ь возвращены методом next. Метод remove удаляет последний элемент, полученный посредством next. Исключение типа NoSuchElementException – он так входит в состав пакета java.util - выбрасывается, если метод next не находит ходящих элементов либо метод remove вызывается еще до первого выполнения next. За подробностями обращайтесь к разделу 16.2 на странице 438.

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

public static Iterator walkThrough(final Object[] objs) {

class Iter implements Iterator {

private int pos = о;

public boolean hasNext() {

return (pos < objs.length);

}

public Object next() throws NoSuchElementException { if (pos >= objs.length)

throw new NoSuchElementException();

return objs[pos++];

}

publie void remove() {

throw new UnsupPortedOperationException();

}

}

return new Iter();

Класс Iter локален по отношению к методу walkThrough и не является членом внешнего класса. Код класса Iter обладает правами доступа ко всем переменным , объявленным в теле метода, в частности к параметру objs. В составе класса явлено поле pos, предназначенное для хранения текущего значения индекса массива objs. (Подразумевается, что в файле с исходным текстом, где расположено явление метода walkThrough, имеется инструкция импорта интерфейса Iterator и класса NoSuchElementException из пакета java.util.)

члены локальных внутренних классов способны перекрывать одноименные локальные переменные и параметры, объявленные в том же блоке, точно так

как поля и методы экземпляра. Но правила, о которых мы говорили в разе 5.2.3 на странице 145, применимы во всех случаях. Единственное отличие состоит в том, что коль скоро локальная переменная или параметр оказались скрыты, способов обращения к ним больше не существует.

1. Анонимные внутренние классы

Когда кажется, что без использования локального внутреннего класса просто обойтись, можно объявить анонимный (безымянный) класс (anonymous inner s), способный расширить другой класс или реализовать интерфейс. Объявления такого класса выполняется одновременно с получением его объекта посредством оператора new. Рассмотрим для примера тот же метод walkThrough, что и предыдущем разделе. Класс Iter достаточно специфичен и совершенно бесполезен за пределами метода walkThrough. Имя класса Iter вряд ли придает коду

148      ГЛАВА 5. ВЛОЖЕННЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

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

publiс statiс Iterator walkThrough(fina1 objeсt[] objs) { return new Iterator() {

private int pos = о;

publiс boolean hasNext() {  I

return (pos < objs.length); }

public Object next() throws NoSuchElementException { if (pos >= objs.length)

throw new NoSuchElementException(); return objs[pos++];

}

public void remove() {

throw new UnsupPortedOperationException(); }

};

}

Анонимный класс определяется непосредственно в выражении оператора new и служит его составной частью. Тип, указанный после new, является базовым по отношению к объявляемому анонимному классу. Поскольку Iterator - это интерфейс, наш анонимный класс косвенным образом расширяет класс Object и реализует интерфейс Iterator. В объявлении анонимного класса нельзя явно задавать предложения extends и implements.

В составе анонимного класса не разрешается объявлять конструкторы, поскольку не существует имени, которым можно было бы их снабдить. Если задача, решаемая классом, сложна и все-таки требует наличия конструкторов, вероятно, целесообразно прибегнуть к модели локального внутреннего класса. Большинство анонимных классов, используемых на практике, обычно не нуждается в серьезной инициализации. Впрочем, те значения, которые следовало бы передавать в виде аргументов конструкторов, всегда можно разместить в соответствующих выражениях и блоках инициализации - применение таких средств в составе анонимного класса вполне допустимо. Единственная проблема стадии конструирования, которая в этой ситуации все еще заслуживает особого внимания, связана с необходимостью явного вызова конструктора базового класса. Она решается оператором new, обеспечивающим корректный вызов такого конструктора. Вернемся к примеру класса Attr, рассмотренному в разделе 3.1 на странице 88, и дополним его объявлением анонимного производного класса, который вызывает конструктор Attr с одним аргументом и переопределяет метод SetValue, обеспечивая вывод на экран нового значения атрибута:

Attr name = new Аttr("имя") {

public Object setValue(Object nv) { Sуstеm.оut.рrintln("значение изменено на " + nv);

return super.setValue(nv);

}

} ;

Обратите внимание, что в примере метода walkThrough неявно вызывался Конструктор без параметров класса Object - это единственный конструктор,

5.4. АНОНИМНЫЕ ВНУТРЕННИЕ КЛАССЫ        149

который может быть использован, если анонимный класс относится к интерфейсному типу.

Анонимные классы, обычно простые и достаточно прямолинейные, способны легко превратиться в предмет, недоступный для понимания. Чем выше степень их вложенности, тем труднее их воспринимать и использовать. Представьте ситуацию: код анонимного класса, который должен выполняться позже, расположен в теле метода, работающего в данный момент, - это потенциальный источник недоразумений. Вероятно, следует избегать использования анонимных классов, объем которых превышает 6-8 строк кода, и обращаться к ним только в самых простых случаях. Мы обошли это правило в примере walkThrough только потому, что единственная задача этого метода - возврат объекта; но если Метод, как предусматривается, должен выполнять много различных функций, следует создавать небольшие анонимные классы, позволяющие сохранять удобочитаемость кода и возможность его развития. Модель анонимных классов, используемая правильно и по назначению, - это хорошее средство описания простых вещей простым языком. В противном случае она способна создавать непостижимые и непреодолимые проблемы.

5.5. Наследование вложенных типов в составе внешних

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

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

abstract class Device {

abstract class Port {

// ...

}

// ...

}

class Printer extends Device {

class SerialPort extends Port { / / ...

}

Port serial = new SerialPort();

}

150         ГЛАВА 5. ВЛОЖЕННЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

Конкретный класс устройства, в свою очередь, может быть расширен совместно с конкретным внутренним классом порта, с целью определения некоторых специальных характеристик:

class НighSpeedPrinter extends printer {

class SerialPort extends Printer.SerialPort { // ...

}

}

Цель, в соответствии с которой класс НighSpeedPrinter.SerialPort переопределяет класс printer.SerialPort, состоит в том, чтобы поле serial получило ссылку на объект корректного типа. Но к классу Рrinter новый производный от SerialPort класс, определенный внутри НighSpeedPrinter, не имеет отношения, хотя и обладает прежним именем.

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

class printer extends Device {

class SerialPort extends Port {

// ... }

Port serial = createSerialPort();

protected Port createSerialPort() {

return new SerialPort();}

}

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

class НighSpeedPrinter extends Printer {

class EnhancedSerialPort extends SerialPort { / / ...

}

protected Port createSerialPort() {

return new EnhancedSerialPort();

}

}

в Процессе конструирования объекта класса НighSpeedPrinter и выполнения инициализаторов класса printer вызывается переопределенная версия метода CreateSerialPort, которая возвращает экземпляр типа EnhancedSerialPort.

Это пример ситуации, когда метод производного класса вызывается еще до того, как объект этого класса полностью сконструирован, и поэтому она заслуживает самого пристально го внимания. Если, скажем, поле в EnhancedSerialPort инициализируется значением поля во внешнем коде НighSpeedPrinter, в момент конструирования объекта класса EnhancedSerialPort поля внешнего объекта все еще будут обладать исходными "нулевыми" значениями, предлагаемыми по умолчанию.

5.5. НАСЛЕДОВАНИЕ ВЛОЖЕННЫХ ТИПОВ В СОСТАВЕ ВНЕШНИХ      151

При другом подходе конструктор для НighSpeedPrinter мог бы просто присвоить полю serial ссылку на объект класса EnhancedSerialРоrt. Такая Мера, однако, привела бы к излишним затратам, связанным с конструированием объекта типа SerialРоrt, которые в общем случае оказались бы ощутимы и нежелательны (в нашем примере это могло бы означать выполнение ненужных операций по настройке аппаратного обеспечения).

5.6. Вложенность в интерфейсах

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

interface Changeable { class Record {

public Object changer;

public string changeDesc;

}

Record getLastChange();

 / / ...

}

Метод getLastChange возвращает объект класса Changeablе. Record. Этот класс имеет значение только в пределах внешнего интерфейса changeab1е, и поэтому "вынесение" его вовне не только не целесообразно, но и явно не желательно, поскольку разделит столь необходимый классу и интерфейсу общий контекст.

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

Вложенные класс или интерфейс, как и другие члены интерфейса, всегда по умолчанию снабжаются признаками public и statiс.

5.6.1. Переменные в интерфейсах, допускающие изменение

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

interface SharedData {

class Data {

 private int x = 0;

 public int getX() {

return x;}

 public void setX(int newX) {

x = newX;}

152       ГЛАВА 5. ВЛОЖЕННЫЕ КЛАССЫ И ИНТЕРФЕЙСЫ

}

Data data = new Data();

}

Теперь в любом коде, реализующем или использующем интерфейс ShareData, возможен совместный доступ к данным посредством ссылки data

5.7. Реализация вложенных типов

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

Как программисту, решающему прикладные задачи, вам необходимо знать только об одной стороне этого процесса, а именно о соглашениях по поводу использования имен. Рассмотрим статический, Т.е. не локальный, вложенный тип, определенный как OuterInner. Это исходное имя класса. На своем уровне виртуальная машина Java выполняет переименование класса в Outer$Inner - по сути, просто заменяя символ точки символом доллара. Что касается локальных внутренних классов, результаты преобразования их имен не слишком важны, поскольку такие классы просто не доступны - в качестве имен могут быть использованы, например, Outer$l, Outer$2 и т.д., а также идентификаторы иного вида.

Со всеми этими вещами вам придется сталкиваться только в двух случаях.

Во-первых, приступая к сборке программного проекта, вы должны быть осведомлены о назначении файлов со "странными" именами, в которых употребляется символ $. Во-вторых, обращаясь к механизму рефлексии (мы расскажем о нем в главе 11) для создания экземпляров вложенных классов, вы обязаны понимать, как преобразуются имена типов. Но в обычной практике программирования подобным вопросам не стоит уделять повышенного внимания.

Нет столь великой вещи, которую не превзошла бы величиною еще большая

вещи столь малой. в которую не вместилась бы еще меньшая

    Козьма Прутков, Плоды раздумья

5.7. РЕАЛИЗАЦИЯ ВЛОЖЕННЫХ ТИПОВ         153




1. Thomas More Utopia
2. ТЕМАТИКИ СІМОГІН Анатолій Анатолійович УДК 519
3. Сергинский многопрофильный техникум ДНЕВНИК ПРОИЗВОДСТВЕННОЙ ПРАКТИКИ по профессиональному моду
4. Реклама стимулирует сбыт хорошего товара и ускоряет провал плохого
5. ГАРАНТ-КАЧЕСТВА г
6. атом Генезис античного атомизма связан с поставленной в Элейской школе проблемой обоснования множества и
7. Организация розничной торговли
8.  Здравствуйте Мы рады приветствовать вас Как много светлых улыбок Мы видим на лицах сейчас В2 Н
9. тема общества или политическая организация общества организованная на единой нормативноценностной осно
10. Проблема гибели лесов
11. Сверхпроводники (Доклад)
12. Загальна характеристика основних економічних моделей
13. місяцьрік Місце народження Країна народження
14.  Отметьте все фразы которые являются высказываниями
15. тематическом профиле
16. НА ТЕМУ- ШАРЛЬ ЛУИ де МОНТЕСКЬЁ и жанр просветительского философского романа Персидские письма
17. Разработка класса
18. Тема- Вегетативна нервова система Виконав- студент 33 групи Чепінога Роман Пл
19. Человеческая индивидуальность наследственность и среда
20. Организация производства мототехники и средств малой механизации на территории свободной экономической з