84
конструкторе и (или) перегруженных конструкторах; безымянные внутренние классы допускают только инициализацию экземпляром.
Другая причина для использования локального внутреннего класса вместо безымянного внутреннего — необходимость создания более чем одного объекта такого класса.
Идентификаторы внутренних классов
Так как каждый класс компилируется в файл с расширением .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. Все выражение должно быть заключено в круглые скобки, чтобы преобразование было выполнено