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