42

42


Параметризованные и типизованные контейнеры
Одна из проблем, существовавших при работе с контейнерами до выхода 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) {
ArrayList<Apple> apples = new ArrayList<Apple>(), forCint i = 0; i < 3; i++)
apples, add (new AppleO); // Ошибка компиляции: // apples.add(new OrangeO); for(int i = 0; i < apples.size(); i++)
System, out. pri ntl n(appl es. get( i) .idO). // Использование синтаксиса foreach: for(Apple с • apples)
System.out.pri nt1n(с.i d());
}
} /* Output: 0 1 2 0 1 2
*///-
На этот раз компилятор не разрешит поместить объекты Orange в контейнер apples, поэтому вы получите ошибку на стадии компиляции (а не на стадии вы­полнения).
Также обратите внимание на то, что выборка данных из List не требует пре­образования типов. Поскольку контейнер знает тип хранящихся в нем элемен­тов, он автоматически выполняет преобразование при вызове get(). Таким обра­зом, параметризация не только позволяет компилятору проверять тип объектов, помещаемых в контейнеры, но и упрощает синтаксис работы с объек­тами в контейнере. Пример также показывает, что, если индексы элементов вам не нужны, для перебора можно воспользоваться синтаксисом foreach.
Вы не обязаны точно соблюдать тип объекта, указанный в качестве парамет­ра типа. Восходящее преобразование работает с параметризованными контей­нерами точно так же, как и с другими типами:
//: hoiding/GenericsAndUpcasting.java import java.util.*;
class   GrannySmith extends Apple {}
class   Gala extends Apple {}
class   Fuji extends Apple {}
class   Braeburn extends Apple {}
public class GenericsAndUpcasting {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<Apple>();
apples.add(new GrannySmithO);
apples.add(new GalaO);
apples.add(new Fuji()):
apples.add(new BraeburnO);
for(Apple с : apples)
System.out.println(c);
}
} /* Output: (Sample) GrannySmi th@7d772e Gala@llb86e7 Fuji@35ce36 Braeburn@757aef *///:-
Мы видим, что в контейнер, рассчитанный на хранение объектов Apple, мож­но помещать объекты типов, производных от Apple.
В результатах, полученных с использованием метода toStringO объекта Ob­ject, выводится имя класса с беззнаковым шестнадцатеричным представлениемхеш-кодаобъекта (сгенерированного методом hashCode()).
Основные концепции
В библиотеке контейнеров Java проблема хранения объектов делится на две концепции, выраженные в виде базовых интерфейсов библиотеки:
Коллекция:группа отдельных элементов, сформированная по некоторым правилам. Класс List (список) хранит элементы в порядке вставки, в клас­се Set (множество) нельзя хранить повторяющиеся элементы, а класс Queue (очередь) выдает элементы в порядке, определяемом спецификой очереди (обычно это порядок вставки элементов в очередь).
Карта:набор пар объектов «ключ-значение», с возможностью выборки по ключу. ArrayList позволяет искать объекты по порядковым номерам, поэтому в каком-то смысле он связывает числа с объектами. Класс Map (карта — также встречаются терминыассоциативный массивисловарь)позволяет искать объекты по другим объектам — например, получить объект значения по объекту ключа, по аналогии с поиском определе­ния по слову.
Хотя на практике это не всегда возможно, в идеале весь программный код должен писаться в расчете на взаимодействие с этими интерфейсами, а точный тип указывается только в точке создания. Следовательно, объект List может быть создан так:
List<Apple> apples = new ArrayList<Apple>():
Обратите внимание на восходящее преобразование ArrayList к List, в отличие от предыдущих примеров. Если позднее вы решите изменить реализацию, дос­таточно сделать это в точке создания:
List<Apple> apples = new LinkedList<Apple>();
Итак, в типичной ситуации вы создаете объект реального класса, повышаете его до соответствующего интерфейса, а затем используете интерфейс во всем остальном коде.
Такой подход работает не всегда, потому что некоторые классы обладают до­полнительной функциональностью. Например, LinkedList содержит дополни­тельные методы, не входящие в интерфейс List, а ТгееМар — методы, не входя­щие в Map. Если такие методы используются в программе, восходящее преоб­разование к обобщенному интерфейсу невозможно.
Интерфейс Collection представляет концепциюпоследовательностикак спо­соба хранения группы объектов. В следующем простом примере интерфейс Collection (представленный контейнером ArrayList) заполняется объектами Integer, с последующим выводом всех элементов полученного контейнера:
//: hoiding/SimpleCol1ection.java
import java.util .*;
public class SimpleCollection {
public static void main(String[] args) {
Collection<Integer> с = new ArrayList<Integer>(); for(int i = 0; i < 10; i++)
c.add(i); // Автоматическая упаковка for(Integer i : c)
System.out.print(i + ". ");
}
} /* Output:
0. 1. 2. 3. 4, 5. 6. 7, 8-. 9.
*///•-
Поскольку в этом примере используются только методы Collection, подойдет объект любого класса, производного от Collection, но ArrayList является самым простейшим типом последовательности.
' Все коллекции поддерживают перебор в синтаксисе foreach, как в приведен­ном примере. Позднее в этой главе будет рассмотрена другая, более гибкая кон­цепцияитераторов.Добавление групп элементов
Семейства Arrays и Collections в java.util содержат вспомогательные методы для включения групп элементов в коллекции. Метод Arrays.asList() получает либо массив, либо список элементов, разделенных запятыми, и преобразует его в объект List. Метод Collections.addAUQ получает объект Collection и либо массив, либо список, разделенный запятыми, и добавляет элементы в Collection. Пример:
//• hoiding/AddingGroups java
// Добавление групп элементов в объекты Collection
import java.util *;
public class AddingGroups {
public static void main(String[] args) { Collection<Integer> collection =
new ArrayList<Integer>(Arrays.asList(l, 2, 3, 4, 5)); Integer[] morelnts = { 6. 7. 8. 9. 10 }; collection.addAll(Arrays.asList(morelnts)); // Работает намного быстрее, но таким способом // невозможно сконструировать Collection: Collections.addAll(collection, 11, 12, 13, 14, 15); Col lections.addAll(collection, morelnts); // Создает список на основе массива: List<Integer> list = Arrays.asList(16. 17, 18, 19, 20); list set(l, 99); // Можно - изменение элемента // list.add(21); // Ошибка времени выполнения - нижележащий // массив не должен изменяться в размерах
}
} ///:-
Конструктор Collection может получать другой объект Collection, используе­мый для его инициализации, поэтому для передачи исходных данных можно воспользоваться методом Arrays.asList(). Однако метод Collections.addAll() рабо­тает намного быстрее, и вы с таким же успехом можете сконструировать Collection без элементов, а затем вызвать Collections.addAll — этот способ счита­ется предпочтительным.
Методу Collection.addAll() в аргументе может передаваться только другой объект Collection, поэтому он уступает в гибкости методам Arrays.asList() и Collections.addAll(), использующим переменные списки аргументов.
Также можно использовать вывод Arrays.asList() напрямую, в виде List, но в этом случае нижележащим представлением будет массив, не допускающий изменения размеров. Вызов add() или delete() для такого списка приведет к по­пытке изменения размера массива, а это приведет к ошибке во время выполне­ния.
Недостаток Arrays.asList() заключается в том, что он пытается «вычислить» итоговый тип List, не обращая внимания на то, что ему присваивается. Иногда это создает проблемы:
//: hoiding/AsListInference.java // Arrays.asListO makes its best guess about type, import java.util.*;
class Snow {}
class Powder extends Snow {} class Light extends Powder {} class Heavy extends Powder {} class Crusty extends Snow {} class Slush extends Snow {}

public class AsListInference {
public static void main(String[] args) { List<Snow> snowl = Arrays.asList(
new CrustyO. new SlushO. new PowderO);
// He компилируется- // List<Snow> snow2 = Arrays.asList( // new LightO. new HeavyO); // Сообщение компилятора: //found java.util.List<Powder> // required, java util List<Snow>
II Collections.addAllО работает нормально:
List<Snow> snow3 = new ArrayList<Snow>():
Col 1 ecti ons. addAl 1 (snow3, new LightO. new HeavyO);
II Передача информации посредством уточнения // типа аргумента
List<Snow> snow4 = Arrays <Snow>asList( new LightO, new HeavyO),
}
} ///:-
При попытке создания snow2, Arrays.asList() создает List<Powder> вместо List <Snow>, тогда как Collections.addAll() работает нормально, потому что целевой тип определяется первым аргументом. Как видно из создания snow4, в вызов Arrays.asList() можно вставить «подсказку», которая сообщает компилятору фак­тический тип объекта List, производимого Arrays.asList().
С контейнерами Map дело обстоит сложнее, и стандартная библиотека Java не предоставляет средств их автоматической инициализации, кроме как по со­держимому другого объекта Map.

Report Page