84

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. Все выражение должно быть заключено в круглые скобки, чтобы преобразование было выполнено

Report Page