Выбрать главу

Контейнеры

Часто бывает заранее неизвестно, сколько объектов потребуется для решения определенной задачи и как долго они будут существовать. Также непонятно, как хранить такие объекты. Сколько памяти следует выделить для хранения этих объектов? Неизвестно, так как эта информация станет доступна только во время работы программы.

Многие проблемы в объектно-ориентированном программировании реша¬ются простым действием: вы создаете еще один тип объекта. Новый тип объекта, решающего эту конкретную задачу, содержит ссылки на другие объекты. Конечно, эту роль могут исполнить и массивы, поддерживаемые в большин¬стве языков. Однако новый объект, обычно называемый контейнером (или же коллекцией, но в Java этот термин используется в другом смысле), будет по не¬обходимости расширяться, чтобы вместить все, что вы в него положите. По¬этому вам не нужно будет знать загодя, на сколько объектов рассчитана ем¬кость контейнера. Просто создайте контейнер, а он уже позаботится о подроб¬ностях.

К счастью, хороший ООП-язык поставляется с набором готовых контейне¬ров. В С++ это часть стандартной библиотеки С++, иногда называемая библио¬текой стандартных шаблонов (Standard Template Library, STL). Smalltalk по¬ставляется с очень широким набором контейнеров. Java также содержит кон¬тейнеры в своей стандартной библиотеке. Для некоторых библиотек считается, что достаточно иметь один единый контейнер для всех нужд, но в других (на¬пример, в Java) предусмотрены различные контейнеры на все случаи жизни: не¬сколько различных типов списков List (для хранения последовательностей эле¬ментов), карты Map (известные также как ассоциативные массивы, позволяют связывать объекты с другими объектами), а также множества Set (обеспечиваю¬щие уникальность значений для каждого типа). Контейнерные библиотеки так¬же могут содержать очереди, деревья, стеки и т. п.

С позиций проектирования, все, что вам действительно необходимо, — это контейнер, способный решить вашу задачу. Если один вид контейнера отвечает всем потребностям, нет основания использовать другие виды. Существует две причины, по которым вам приходится выбирать из имеющихся контейнеров. Во-первых, контейнеры предоставляют различные интерфейсы и возможности взаимодействия. Поведение и интерфейс стека отличаются от поведения и ин¬терфейса очереди, которая ведет себя по-иному, чем множество или список. Один из этих контейнеров способен обеспечить более эффективное решение вашей задачи в сравнении с остальными. Во-вторых, разные контейнеры по-разному выполняют одинаковые операции. Лучший пример — это ArrayList и LinkedList. Оба представляют собой простые последовательности, которые мо¬гут иметь идентичные интерфейсы и черты поведения. Но некоторые операции значительно отличаются по времени исполнения. Скажем, время выборки про¬извольного элемента в ArrayList всегда остается неизменным вне зависимости от того, какой именно элемент выбирается. Однако в LinkedList невыгодно рабо¬тать с произвольным доступом — чем дальше по списку находится элемент, тем большую задержку вызывает его поиск. С другой стороны, если потребуется вставить элемент в середину списка, LinkedList сделает это быстрее ArrayList. Эти и другие операции имеют разную эффективность, зависящую от внутренней структуры контейнера. На стадии планирования программы вы можете вы¬брать список LinkedList, а потом, в процессе оптимизации, переключиться на ArrayList. Благодаря абстрактному характеру интерфейса List такой переход потребует минимальных изменений в коде.

Параметризованные типы 35

Параметризованные типы

До выхода Java SE5 в контейнерах могли храниться только данные Object — единственного универсального типа Java. Однокорневая иерархия означает, что любой объект может рассматриваться как Object, поэтому контейнер с элемен¬тами Object подойдет для хранения любых объектов1.

При работе с таким контейнером вы просто помещаете в него ссылки на объ¬екты, а позднее извлекаете их. Но если контейнер способен хранить только Object, то при помещении в него ссылки на другой объект происходит его преоб¬разование к Object, то есть утрата его «индивидуальности». При выборке вы полу¬чаете ссылку на Object, а не ссылку на тип, который был помещен в контейнер. Как же преобразовать ее к конкретному типу объекта, помещенного в контейнер?

Задача решается тем же преобразованием типов, но на этот раз тип изменя¬ется не по восходящей (от частного к общему), а по нисходящей (от общего к частному) линии. Данный способ называется нисходящим преобразованием. В случае восходящего преобразования известно, что окружность есть фигура, поэтому преобразование заведомо безопасно, но при обратном преобразовании невозможно заранее сказать, представляет ли экземпляр Object объект Circle или Shape, поэтому нисходящее преобразование безопасно только в том случае, если вам точно известен тип объекта.

Впрочем, опасность не столь уж велика — при нисходящем преобразовании к неверному типу произойдет ошибка времени исполнения, называемая исклю¬чением (см. далее). Но при извлечении ссылок на объекты из контейнера необ¬ходимо каким-то образом запоминать фактический тип их объектов, чтобы вы¬полнить верное преобразование.

Нисходящее преобразование и проверки типа во время исполнения требуют дополнительного времени и лишних усилий от программиста. А может быть, можно каким-то образом создать контейнер, знающий тип хранимых объектов, и таким образом устраняющий необходимость преобразования типов и потен¬циальные ошибки? параметризованные типы представляют собой классы, ко¬торые компилятор может автоматически адаптировать для работы с определен¬ными типами. Например, компилятор может настроить параметризованный контейнер на хранение и извлечение только фигур (Shape).

Одним из важнейших изменений Java SE5 является поддержка параметри¬зованных типов (generics). Параметризованные типы легко узнать по угловым скобкам, в которые заключаются имена типов-параметров; например, контейнер ArrayList, предназначенный для хранения объектов Shape, создается следующим образом:

ArrayList<Shape> shapes = new ArrayList<Shape>(),

Многие стандартные библиотечные компоненты также были изменены для использования обобщенных типов. Как вы вскоре увидите, обобщенные типы встречаются во многих примерах программ этой книги.

Создание, использование объектов и время их жизни

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

Допустим, например, что вы разрабатываете систему для управления движе¬нием авиатранспорта. (Эта же модель пригодна и для управления движением тары на складе, или для системы видеопроката, или в питомнике для бродячих животных.) Сначала все кажется просто: создается контейнер для самолетов, затем строится новый самолет, который помещается в контейнер определенной зоны регулировки воздушного движения. Что касается освобождения ресурсов, соответствующий объект просто уничтожается при выходе самолета из зоны слежения.

Но возможно, существует и другая система регистрации самолетов, и эти данные не требуют такого пристального внимания, как главная функция управ¬ления. Может быть, это записи о планах полетов всех малых самолетов, поки¬дающих аэропорт. Так появляется второй контейнер для малых самолетов; ка¬ждый раз, когда в системе создается новый объект самолета, он также включает¬ся и во второй контейнер, если самолет является малым. Далее некий фоновый процесс работает с объектами в этом контейнере в моменты минимальной заня¬тости.

Теперь задача усложняется: как узнать, когда нужно удалять объекты? Даже если вы закончили работу с объектом, возможно, с ним продолжает взаимодействовать другая система. Этот же вопрос возникает и в ряде других ситуаций, и в программных системах, где необходимо явно удалять объекты после завершения работы с ними (например, в С++), он становится достаточ¬но сложным.