Философия Java3
Шрифт:
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, без ошибок компиляции или времени выполнения. Но при вызове
Параметризованные и типизованные контейнеры 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 объекта Object, выводится имя класса с беззнаковым шестнадцатеричным представлением хеш-кода объекта (сгенерированного методом 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, как в приведенном примере. Позднее в этой главе будет рассмотрена другая, более гибкая концепция итераторов.