47

47


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, я буду употреблять общий тер­мин «контейнер»). Контейнеры обладают весьма изощренными возможностями для хранения объектов и работы с ними, и с их помощью удается решить огром­ное количество задач.
Параметризованные и типизованные контейнеры
Одна из проблем, существовавших при работе с контейнерами до выхода Java SE5, заключалась в том, что компилятор позволял вставить в контейнер объект неверного типа. Для примера рассмотрим один из основных рабочих контейнеров
ArrayList, в котором мы собираемся хранить объекты Apple. Пока рассматривайте ArrayList как «автоматически расширяемый массив». Работать с ним несложно: создайте объект, вставляйте объекты методом add(), обращайтеь к ним мето­дом get(), используйте индексирование — так же, как для массивов, но без квад­ратных скобок. ArrayList также содержит метод size(), который возвращает теку­щее количество элементов в массиве.
В следующем примере в контейнере размещаются объекты Apple и Orange, которые затем извлекаются из него. Обычно компилятор Java выдает предупре­ждение, потому что в данном примере не используется параметризация, однако в Java SE5 существует специальнаядиректива@SuppressWarnings для подавле­ния предупреждений. Директивы начинаются со знака @ и могут получать ар­гументы; в данном случае аргумент означает, что подавляются только «непро­веряемые» предупреждения:
//: hoiding/ApplesAndOrangesWithoutGenerics. java
// Простой пример работы с контейнером
// (компилятор выдает предупреждения).
// {ThrowsException}
import java.util.*;
class Apple {
private static long counter; private final long id = counter++; public long id() { return id; }
}
class Orange {}
public class ApplesAndOrangesWithoutGenerics { @SuppressWarni ngs("unchecked") public static void main(String[] args) {
ArrayList apples = new ArrayListO; for(int i = 0; i < 3; i++)
apples, add (new AppleO); // He препятствует добавлению объекта Orange: apples.add(new OrangeO); for(int i = 0; i < apples.size(). i++) ((Apple)apples.get(i)).id();
// Объект Orange обнаруживается только во время выполнения
}
}
///:-
Директивы Java SE5 будут рассмотрены позднее.
Apple и Orange — совершенно разные классы; они не имеют ничего общего, кроме происхождения от Object (напомню: если в программе явно не указан ба­зовый класс, то в этом качестве используется Object). Так как в ArrayList хранят­ся объекты Object, метод add() может добавлять в контейнер не только объекты Apple, но и Orange, без ошибок компиляции или времени выполнения. Но при вызове метода get() класса ArrayList вы вместо объекта Apple получаете ссылку на Object, которую необходимо преобразовать в Apple. Все выражение должно быть заключено в круглые скобки, чтобы преобразование было выполнено

Параметризованные и типизованные контейнеры279перед вызовом метода id() класса Apple. Во время выполнения, при попытке преобразования объекта Orange в Apple, произойдет исключение.
В главе «параметризованные типы» вы узнаете, чтосозданиеклассов, исполь­зующих механизм параметризации, может быть довольно сложной задачей. С другой стороны, сприменениемготовых параметризованных классов проблем обычно не бывает. Например, чтобы определить объект ArrayList, предназначен­ный для хранения объектов Apple, достаточно использовать вместо имени ArrayList запись А г ray Li s t< A p p le>. В угловых скобках перечисляютсяпараметры типов(их может быть несколько), указывающие тип объектов, хранящихся в данном экземпляре контейнера.
Механизм параметризации предотвращает занесение объектов неверного типа в контейнер на стадии компиляции. Рассмотрим тот же пример, но с ис­пользованием параметризации:
// hoiding/ApplesAndOrangesWithGenerics java import java.util.*;
public class ApplesAndOrangesWithGenerics { public static void main(String[] args) {

Report Page