41

41


В основном классы Event похожи друг на друга, однако классы Bell и Restart представляют собой особые случаи. Bell выдает звуковой сигнал и добавляет себя в список событий, чтобы звонок позднее сработал снова. Заметьте, что внутренние классы действуютпочтикак множественное наследование: классы Bell и Restart имеют доступ ко всем методам класса Event, а также ко всем мето­дам внешнего класса GreenhouseControls.



Классу Restart передается массив объектов Event, которые он добавляет в контроллер. Так как Restart также является объектом Event, вы можете доба­вить этот объект в список событий в методе Restart.action(), чтобы система регу­лярно перезапускалась.



Следующий класс настраивает систему, создавая объект GreenhouseControls и добавляя в него разнообразные типы объектов Event. Это пример шаблона проектирования «команда»— каждый объект в EventList представляет собой за­прос, инкапсулированный в объекте:



//: с08:GreenhouseControl1er.java // Настраивает и запускает систему управления. // {Args: 5000}



import innerclasses.control 1er.*:



public class GreenhouseController {



public static void main(String[] args) {



GreenhouseControls gc = new GreenhouseControls(); // Вместо жесткого кодирования фиксированных данных // можно было бы считать информацию для настройки // из текстового файла: gc.addEvent(gc.new Bel 1 (900)): Event[] eventList = {



gc.new ThermostatNight(O), gc.new Light0n(200), gc.new LightOff(400), gc.new WaterOn(600), gc.new WaterOff(800), gc.new ThermostatDay(1400)



}:



gc.addEvent(gc.new Restart(2000, eventList)): if(args.length == 1) gc.addEvent(



new GreenHouseControls.Terminate( new Integer(args[0])));



gc.runO;



}



} * Output: Вам!



Термостат использует ночной режим



Свет включен



Свет выключен



Полив включен



Полив отключен



Термостат использует дневной режим Перезапуск системы Отключение ///:-



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



Этот пример поможет понять всю ценность механизма внутренних классов, особенно в случае с системами управления.



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



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



//: innerclasses/Inheritlnner.java // Наследование от внутреннего класса.



class Withinner { class Inner {}



}



public class Inheritlnner extends Withlnner.Inner { //! InheritlnnerO {} // He компилируется InheritInner(WithInner wi) { wi.super();



}



public static void main(String[] args) { Withlnner wi = new WithlnnerO; Inheritlnner ii = new Inheritlnner(wi);



}



} ///:-



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



ссылкаНаОбъемлющийКласс.super();



в теле конструктора. Оно обеспечит недостающую ссылку, и программа отком- пилируется.



Можно ли переопределить внутренний класс?



Что происходит, если вы создаете внутренний класс, затем наследуете от его внеш­него класса, а после этого заново описываете внутренний класс в производном






Можно ли переопределить внутренний класс?273классе? Другими словами, можно ли переопределить внутренний класс? Это было бы довольно интересно, но «переопределение» внутреннего класса, как если бы он был еще одним методом внешнего класса, фактически не имеет ни­какого эффекта:



//. innerclasses/BigEgg.java // Внутренний класс нельзя переопределить // подобно обычному методу, import static net.mindview util.Print.*:



class Egg {



private Yolk y; protected class Yolk {



public YolkO { printCEgg.YolkO"). }



}



public EggO {



printC'New EggO"): у = new YolkO:












public class BigEgg extends Egg { public class Yolk {



public YolkO { print("BigEgg YolkO"): }



}



public static void main(String[] args) { new BigEggO;



}



} /* Output New EggO Egg. YolkO *///•-



Конструктор по умолчанию автоматически синтезируется компилятором, а в нем вызывается конструктор по умолчанию из базового класса. Можно по­думать, что при создании объекта BigEgg должен использоваться «переопреде­ленный» класс Yolk, но это отнюдь не так, как видно из результата работы про­граммы.



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



//: innerclasses/BigEgg2.java



// Правильное наследование внутреннего класса,



i mport stati с net.mi ndvi ew.uti1.Pri nt.*;



class Egg2 {



protected class Yolk {



public YolkO { print("Egg2.YolkO"): } public void f() {



print("Egg2 Yolk.fO"):}



}



private Yolk у = new YolkO,продолжение &public Egg2() { print("New Egg2()"); } public void insertYolk(Yolk yy) { у = yy; } public void g() { y.f(); }



}



public class BigEgg2 extends Egg2 {



public class Yolk extends Egg2 Yolk {



publicYolkO{ print("BigEgg2.Yolk()"); }



public void f() { System.out.println("BigEgg2.Yolk.f()"); }



}



public BigEgg2() { insertYolk(new YolkO); } public static void main(String[] args) { Egg2 e2 = new BigEgg2(); e2.g();



}



} /* Output: Egg2. YolkO New Egg2() Egg2. YolkO BigEgg2. YolkO BigEgg2.Yolk.f() *///•-



Теперь класс BigEgg2.Yolk явно расширяет класс Egg2.Yolk и переопределяет его методы. Метод insertYolk() позволяет классу BigEgg2 повысить один из своих объектов Yolk до ссылки у в классе Egg2, поэтому при вызове y.f() в методе д() используется переопределенная версия f(). Второй вызов Egg2.Yolk() — это вы­зов конструктора базового класса из конструктора класса BigEgg2.Yolk. Мы так­же видим, что при вызове метода д() используется «обновленная» версия мето- даЮ.



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



Как было замечено ранее, внутренние классы также могут создаваться в блоках кода — чаще всего в теле метода. Локальный внутренний класс не может иметь спецификатора доступа, так как он не является частью внешнего класса, но для него доступны все неизменные (final) переменные текущего блока и все члены внешнего класса. Следующий пример сравниваетпроцессысоздания локально­го внутреннего класса и безымянного внутреннего класса:



//: innerclasses/LocalInnerClass.java // Хранит последовательность объектов, import static net.mindview.util.Print.*;



interface Counter { int nextO;



}



public class LocalInnerClass { private int count = 0; Counter getCounter(final String name) { // Локальный внутренний класс: class Local Counter implements Counter { public Local CounterО {



return new Local CounterО;



}



// To же самое с безымянным внутренним классом: Counter getCounter2(final String name) { return new CounterO {



// У безымянного внутреннего класса не может быть // именованного конструктора, «легальна» только



// инициализация экземпляром: {



print ("Counter О");



}



public int next О {



printnb(name): // неизменный аргумент return count++:












}



public static void main(String[] args) {



LocalInnerClass lie = new LocalInnerClassO: Counter



cl = lic.getCounter(" локальный"), c2 = lic.getCounter2(" безымянный"): for(int i = 0: i < 5: i++) print(cl.nextO): for(int i = 0: i < 5: i++) print(c2.next());



}



}



} /* Output: Local CounterO CounterO локальный 0 локальный 1 локальный 2 локальный 3 локальный 4 безымянный 5 безымянный 6 безымянный 7 безымянный 8 безымянный 9 *///:-



// У локального внутреннего класса // может быть собственный конструктор: pri nt("Local Counter()");



}



public int next О {



printnb(name): // неизменный аргумент return count++;






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



конструкторе и (или) перегруженных конструкторах; безымянные внутренние классы допускают только инициализацию экземпляром.



Другая причина для использования локального внутреннего класса вместо безымянного внутреннего — необходимость создания более чем одного объекта такого класса.



Идентификаторы внутренних классов



Так как каждый класс компилируется в файл с расширением .class, содержащий полную информацию о создании его экземпляров (эта информация помещается в «мета-класс», называемый объектом Class), напрашивается предположение, что внутренние классы также создают файлы .class для хранения информации освоихобъектах Class. Имена этих файлов-классов строятся по жестко задан­ной схеме: имя объемлющего внешнего класса, затем символ $ и имя внутрен­него класса. Например, для программыLocallnnerClass.javaсоздаются следую­щие файлы с расширением .class:



Counter.class



LocalInnerClass$2.class



LocalInnerClass$lLocalCounter.class



LocalInnerClass.class



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



Хотя такая схема построения внутренних имен проста и прямолинейна, она вполне надежна и работает практически в любых ситуациях. Так как она явля­ется стандартной для языка Java, все получаемые файлы автоматически стано­вятся платформно-независимыми.



Резюме



Интерфейсы и внутренние классы — весьма нетривиальные концепции, и во мно­гих других объектно-ориентированных языках вы их не найдете. Например, в С++ нет ничего похожего. Вместе они решают те задачи, которые С++ пыта­ется решить с применением множественного наследования. Однако множест­венное наследование С++ создает массу проблем; по сравнению с ним интер­фейсы и внутренние классы Java гораздо более доступны.



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









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



[2]
Близкий аналог вложенных классов С++, за тем исключением, что в Java вложенные классы спо­собны обращаться к закрытым членам внешнего класса.



[3]
Я всегда решал эту задачу с особым удовольствием; она впервые появилась в одной из первых моих книгС++ Inside & Out, но Java-реализация выглядит гораздо элегантнее.




Коллекции объектов

О
граниченное количество объектов с фиксированным временем жизни характер­но разве что для относительно простых программ.



В основном ваши программы будут создавать новые объекты на основании критериев, которые станут известны лишь во время их работы. До начала вы­полнения программы вы не знаете ни количества, ни даже типов нужных вам объектов. Следовательно, использовать именованную ссылку для каждого из возможных объектов не удастся:



МуТуре aReference;



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



В большинстве языков существуют некоторые пути решения этой крайне насущной задачи. В Java предусмотрено несколько способов хранения объектов (или, точнее, ссылок на объекты). Встроенным типом является массив, который мы уже рассмотрели. Библиотека утилит Java (Java.utiL*) также содержит дос­таточно полный наборклассов контейнеров(также известных, какклассы кол­лекций, но, поскольку имя Collection (коллекция) используется для обозначения определенного подмножества библиотеки Java, я буду употреблять общий тер­мин «контейнер»). Контейнеры обладают весьма изощренными возможностями для хранения объектов и работы с ними, и с их помощью удается решить огром­ное количество задач.

Report Page