37

37


В общем случае рекомендуетсяотдавать предпочтение классам перед ин­терфейсами.Начните с классов, а если необходимость интерфейсов станет оче­видной — переработайте архитектуру своего проекта. Интерфейсы — замеча­тельный инструмент, но ими нередко злоупотребляют.







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

О
пределение класса может размещаться внутри определения другого класса. Такие классы называютсявнутренними(inner class).



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



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



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



Создание внутренних классов



Внутренние классы создаются в точности так, как и следовало ожидать, — опре­деление класса размещается внутри окружающего класса:



//: innerclasses/Parcel 1.java // Создание внутренних классов.



public class Parcel 1 {






class Contents {



private int i = 11,



public int valueO { return i; }



}



class Destination {



private String label, DestinationCString whereTo) { label = whereTo;



}



String readLabeK) { return label; }



}



// Использование внутренних классов имеет много общего // с использованием любых других классов в пределах Parcel 1: public void shipCString dest) {



Contents с = new ContentsO; Destination d = new Destination(dest); System.out.pri ntln(d readLabel()),



}



public static void main(String[] args) { Parcel 1 p = new Parcel 10. p.ship("Тасмания").



}



} /* Output: Тасмания *///:-



Если вам понадобится создать объект внутреннего класса где-либо, кроме как в не-статическом методе внешнего класса, тип этого объекта должен зада­ваться в форматеИмяВнешнегоКласса.ИмяВнутреннегоКласса, что и делается в методе main().



Связь с внешним классом



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



//: innerclasses/Sequence,java // Хранение последовательности объектов



interface Selector { boolean endO, Object currentO; void nextO;



}



public class Sequence {



private Object[] objects;



private int next = 0,



public Sequence(int size) { items = new Object[size], }



public void add(Object x) {



if(next < items length)



iterns[next++] = x,



}



private class SequenceSelector implements Selector { private int i = 0.



public boolean endО { return i == items.length; } public Object current О { return i terns [ i D: } public void nextО { if(i < items.length) i++; }



}



public Selector selectorO {



return new SequenceSelectorO;



}



public static void main(String[] args) {



Sequence sequence = new Sequence(lO); for(int i = 0, i < 10; i++)



sequence.add(Integer.toString(i)), Selector selector = sequence getSelectorO; whileC!selector endO) {



System.out.println(selector.currentО + " "); selector.nextO;



}



}



} /* Output- 0 1 2 3 4 5 6 7 8 9 *///:-



Класс Sequence — не более чем «оболочка» для массива с элементами Object, имеющего фиксированный размер. Для добавления новых объектов в конец по­следовательности (при наличии свободного места) используется метод add(). Для выборки каждого объекта в последовательности Sequence предусмотрен ин­терфейс с именем Selector. Он позволяет узнать, достигнут ли конец последова­тельности (метод end()), обратиться к текущему объекту (метод current()) и перей­ти к следующему объекту последовательности (метод next()). Так как Selector является интерфейсом, другие классы вправе реализовать его по-своему, а переда­ча его в параметре методов повышает универсальность кода.



Здесь SequenceSelector является закрытым (private) классом, предоставляю­щим функциональность интерфейса Selector. В методе main() вы можете наблю­дать за процессом создания последовательности с последующим заполнением ее объектами String. Затем вызывается метод getSelector() для получения интер­фейса Selector, который используется для перемещения по последовательности и выбора ее элементов.



На первый взгляд создание SequenceSelector напоминает создание обычного внутреннего класса. Но присмотритесь к нему повнимательнее. Заметьте, что в каждом из методов end(), current() и next() присутствует ссылка на items, а это не одно из полей класса SequenceSelector, а закрытое (private) поле объемлющего класса. Внутренний класс может обращаться ко всем полям и методам внешне­го класса-оболочки, как будто они описаны в нем самом. Это весьма удобно, и вы могли в этом убедиться, изучая рассмотренный пример.



Итак, внутренний класс автоматически получает доступ к членам объемлю­щего класса. Как же это происходит? Внутренний класс содержит скрытую ссылку на определенный объект окружающего класса, ответственный за его создание. При обращении к члену окружающего класса используется эта (скрытая) ссылка. К счастью, все технические детали обеспечиваются компиля­тором, но теперь вы знаете, что объект внутреннего класса можно создать толь­ко в сочетании с объектом внешнего класса (как будет показано позже, если внутренний класс не является статическим). Конструирование объекта внут­реннего класса требует наличия ссылки на объект внешнего класса; если ссыл­ка недоступна, компилятор выдаст сообщение об ошибке. Большую часть вре­мени весь процесс происходит без всякого участия со стороны программиста.



Конструкции .this и .new



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



//: innerclasses/DotThis.java



// Обращение к объекту внешнего класса.



public class DotThis {



void f() { System.out.pnntlnCDotThis.fО"); } public class Inner {



public DotThis outer() {



return DotThis this.



// A plain "this" would be Inner's "this"



}



}



public Inner inner О { return new InnerO; } public static void main(String[] args) { DotThis dt = new DotThisO; DotThis Inner dti = dt.innerO; dti.outer().f();



}



} /* Output:



DotThis.f()



*///:-



Иногда бывает нужно приказать другому объекту создать объект одного из его внутренних классов. Для этого перед .new указывается ссылка на другой объект внешнего класса:



//: innerclasses/DotNew.java



// Непосредственное создание внутреннего класса в синтаксисе .new



public class DotNew {



public class Inner {}



public static void main(String[] args) {






DotNew dn - new DotNew(); DotNew.Inner dni = dn.new InnerO;



}



} III -



При создании объекта внутреннего класса указывается не имя внешнего класса DotNew, как можно было бы ожидать, а имяобъектавнешнего класса. Это также решает проблему видимости имен для внутреннего класса, поэтому мы не используем (а вернее, не можем использовать) запись вида dn.new DotNew. Inner().



Невозможно создать объект внутреннего класса, не имея ссылки на внешний класс. Но если создатьвложенный класс(статический внутренний класс), ссыл­ка на объект внешнего класса не нужна.



Рассмотрим пример использования .new в примере Parcel:



// innerclasses/Parcel3 java



// Использование new для создания экземпляров внутренних классов



public class Parcel3 { class Contents {



private int i = 11,



public int valueO { return i; }



}



class Destination {



private String label;



DestinationCString whereTo) { label = whereTo; } String readLabelO { return label; }



}



public static void main(String[] args) { Parcel3 p = new Parcel3(); II Для создания экземпляра внутреннего класса // необходимо использовать экземпляр внешнего класса: Pa reel 3. Contents с = p. new ContentsO; Parcel3.Destination d = p new Destination"Танзания");



}



} III -



Внутренние классы и восходящее преобразование



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



Для начала определим интерфейсы для предыдущих примеров:



// i nnerclasses/Desti nati on.java public interface Destination {



String readLabel(); } Hi­ll-. innerclasses/Contents.java public interface Contents {



int valueO; } ///-



Теперь интерфейсы Contents и Destination доступны программисту-клиенту. (Помните, что в объявлении interface все члены класса автоматически являются открытыми (public).)



При получении из метода ссылки на базовый класс или интерфейс возмож­ны ситуации, в которых вам не удастся определить ее точный тип, как здесь:



//. innerclasses/TestParcel.java



class Parcel4 {



private class PContents implements Contents { private int i = 11; public int valueO { return i; }



}



protected class PDestination implements Destination { private String label; private PDestination(String whereTo) { label = whereTo;



}



public String readLabelО { return label; }



}



public Destination destination(String s) { return new PDestination(s);



}



public Contents contents О {



return new PContentsО;












public class TestParcel {



public static void main(String[] args) { Parcel4 p = new Parcel4(); Contents с = p.contents О; Destination d = p.destinationC'TacMaHMfl"); // Запрещено - нет доступа к private-классу: //! Parcel4.PContents pc = p.new PContentsО;



}



} ///-



В класс Parcel4 было добавлено кое-что новое: внутренний класс PContents является закрытым (private), поэтому он недоступен для всех, кроме внешнего класса Рагсе14. Класс PDestination объявлен как protected, следовательно, доступ к нему имеют только класс Parcel4, классы из одного пакета с Рагсе14 (так как спецификатор protected также дает доступ з пределах пакета) и наследники класса Рагсе14. Таким образом, программист-клиент обладает ограниченной ин­формацией и доступом к этим членам класса. Более того, нельзя даже выпол­нить нисходящее преобразование к закрытому (private) внутреннему классу (или protected, кроме наследников), поскольку его имя недоступно, как показано в классе Test. Таким образом, закрытый внутренний класс позволяет разработ­чику класса полностью запретить использование определенных типов и скрыть все детали реализации класса. Вдобавок, расширение интерфейса с точки зре­ния программиста-клиента не будет иметь смысла, поскольку он не сможет по­лучить доступ к дополнительным методам, не принадлежащим к открытой части класса. Наконец, у компилятора Java появится возможность оптимизи­ровать код.



Внутренние классы в методах и областях действия



Ранее мы рассмотрели ряд типичных применений внутренних классов. В ос­новном ваш код будет содержать «простые» внутренние классы, смысл которых понять нетрудно. Однако синтаксис внутренних классов скрывает множество других, не столь тривиальных способов их использования: внутренние классы можно создавать внутри метода или даже в пределах произвольного блока. На то есть две причины:

Report Page