Глава 10
КОЛЛЕКЦИИ
Программирование заставило дерево зацвести.
Алан Дж. Перлис
Общие определения
Коллекции — это хранилища или контейнеры, поддерживающие различные
способы накопления и упорядочения объектов с целью обеспечения возможно-
стей эффективного доступа к ним. Они представляют собой реализацию аб-
страктных структур данных, поддерживающих три основные операции:
• добавление нового элемента в коллекцию;
• удаление элемента из коллекции;
• изменение элемента в коллекции.
В качестве других операций могут быть реализованы следующие: заменить,
просмотреть элементы, подсчитать их количество и др.
Применение коллекций обусловливается возросшими объемами обрабаты-
ваемой информации. Когда счет используемых объектов идет на сотни тысяч,
массивы не обеспечивают ни должной скорости, ни экономии ресурсов.
Современные информационные системы тестируются на примере электронно-
го магазина, содержащего около 40 тысяч товаров и 125 миллионов клиентов,
сделавших 400 миллионов заказов.
Примером коллекции является стек (структура LIFO — Last In First Out), в ко-
тором всегда удаляется объект, вставленный последним. Для очереди (структура
FIFO — First In First Out) используется другое правило удаления: всегда удаляет-
ся элемент, вставляемый первым. В абстрактных типах данных существует не-
сколько видов очередей: двусторонние очереди, кольцевые очереди, обобщен-
ные очереди, в которых запрещены повторяющиеся элементы. Стеки и очереди
могут быть реализованы как на базе массива, так и на базе связного списка.
Коллекции в языке Java объединены в библиотеке классов java.util и пред-
ставляют собой контейнеры для хранения и манипулирования объектами.
До появления Java 2 эта библиотека содержала классы только для работы
с простейшими структурами данных: Vector, Stack, Hashtable, BitSet, а также
интерфейс Enumeration для работы с элементами этих классов. Коллекции, поя-
вившиеся в Java 2, представляют собой общую технологию хранения и доступа
к объектам. Скорость обработки коллекций повысилась по сравнению с предыдущей
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
254
версией языка за счет отказа от их потокобезопасности. Поэтому, если объект
коллекции может быть доступен из различных потоков, что наиболее естествен-
но для распределенных приложений, необходимо использовать коллекции из Java 1.
Так как в коллекциях при практическом программировании хранится набор
ссылок на объекты одного типа, следует обезопасить коллекцию от появления
ссылок на другие, не разрешенные логикой приложения типы. Такие ошибки
при использовании нетипизированных коллекций выявляются на стадии вы-
полнения, что повышает трудозатраты на исправление и верификацию кода.
Поэтому, начиная с версии Java SE 5, коллекции стали типизированными.
Более удобным стал механизм работы с коллекциями, а именно:
• предварительное сообщение компилятору о типе ссылок, которые будут
храниться в коллекции, при этом проверка осуществляется на этапе компи-
ляции;
• отсутствие необходимости постоянно преобразовывать возвращаемые
по ссылке объекты (тип Object) к требуемому типу.
Структура коллекций характеризует способ, с помощью которого програм-
мы Java обрабатывают группы объектов. Так как Object — суперкласс для всех
классов, то в коллекции можно хранить объекты любого типа, кроме базовых.
Коллекции — это динамические массивы, связные списки, деревья, множест-
ва, хэш-таблицы, стеки, очереди.
Интерфейсы коллекций:
Map — карта отображения вида «ключ-значение»;
Collection — вершина иерархии коллекций List, Set;
List — специализирует коллекции для обработки списков;
Set — специализирует коллекции для обработки множеств, содержа-
щих уникальные элементы.
Все классы коллекций реализуют также интерфейсы Serializable, Cloneable
(кроме WeakHashMap). Кроме того, классы, реализующие интерфейсы
List и Set, реализуют также интерфейс Iterable.
В интерфейсе Collection определены методы, которые работают на всех
коллекциях:
boolean add(E obj) — добавляет obj к вызывающей коллекции и возвраща-
ет true, если объект добавлен, и false, если obj уже элемент коллекции;
boolean remove(Object obj) — удаляет obj из коллекции;
boolean addAll(Collection extends E> c) — добавляет все элементы кол-
лекции к вызывающей коллекции;
void clear() — удаляет все элементы из коллекции;
boolean contains(Object obj) — возвращает true, если вызывающая коллек-
ция содержит элемент obj;
boolean equals(Object obj) — возвращает true, если коллекции эквивалентны;
boolean isEmpty() — возвращает true, если коллекция пуста;
Iterator iterator() — извлекает итератор;
КОЛЛЕКЦИИ
255
int size() — возвращает количество элементов в коллекции;
Object[] toArray() — копирует элементы коллекции в массив объектов;
T[] toArray(T a[]) — копирует элементы коллекции в массив объектов
определенного типа.
При работе с элементами коллекции применяются следующие интерфейсы:
Comparator, Comparable — для сравнения объектов;
Iterator, ListIterator, Map.Entry — для перечисления и до-
ступа к объектам коллекции.
Интерфейс Iterator используется для построения объекта, который
обеспечивает доступ к элементам коллекции. К этому типу относится объект,
возвращаемый методом iterator(). Такой объект позволяет просматривать со-
держимое коллекции последовательно, элемент за элементом. Позиции итера-
тора располагаются в коллекции между элементами. В коллекции, состоящей
из N элементов, существует N+1 позиций итератора.
Методы интерфейса Iterator:
boolean hasNext() — проверяет наличие следующего элемента, а в случае
его отсутствия (завершения коллекции) возвращает false. Итератор при этом
остается неизменным;
E
next() — возвращает ссылку на объект, на который указывает итера-
тор, и передвигает текущий указатель на следующий, предоставляя доступ
к следующему элементу. Если следующий элемент коллекции отсутствует,
то метод next() генерирует исключение NoSuchElementException;
void remove() — удаляет объект, возвращенный последним вызовом метода
next(). Если метод next() до вызова remove() не вызывался, то будет сгенериро-
вано исключение IllegalStateException.
Интерфейс Map.Entry предназначен для извлечения ключей и значений кар-
ты с помощью методов K getKey() и V getValue() соответственно. Вызов мето-
да V setValue(V value) заменяет значение, ассоциированное с текущим ключом.
Списки
Класс ArrayList — динамический массив объектных ссылок. Реализует
интерфейсы List, Collection, Iterable.
java.util.AbstractCollection
java.util.AbstractList
java.util.ArrayList
В классе объявлены конструкторы:
ArrayList()
ArrayList(Collection extends E> c)
ArrayList(int capacity)
Практически все методы класса являются реализацией абстрактных мето-
дов из суперклассов и интерфейсов. Методы интерфейса List позволяют
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
256
вставлять и удалять элементы из позиций, указываемых через отсчитываемый
от нуля индекс:
void add(int index, E element) — вставляет element в позицию, указанную
в index;
E
remove(int index) — удаляет объект из позиции index;
E
set(int index, E element) — заменяет объект в позиции index, возвращает
при этом удаляемый элемент;
void addAll(int index, Collection extends E> c) — вставляет в вызываю-
щий список все элементы коллекции с, начиная с позиции index;
E
get(int index) — возвращает элемент в виде объекта из позиции index;
int indexOf(Object ob) — возвращает индекс указанного объекта;
List subList(int fromIndex, int toIndex) — извлекает часть коллекции
в указанных границах.
Удаление и добавление элементов для такой коллекции представляет собой
ресурсоемкую задачу, поэтому объект ArrayList лучше всего подходит для
хранения списков с малым числом подобных действий. С другой стороны, на-
вигация по списку осуществляется очень быстро, поэтому операции поиска
производятся за более короткое время.
/* # 1 # создание параметризованной коллекции # DemoGeneric.java */
package
by.bsu.collect;
import
java.util.ArrayList;
public
class DemoGeneric {
public static void main(String args[ ]) {
ArrayList list = new ArrayList<>();
// ArrayList b = new ArrayList(); // ошибка компиляции
list.add("Java");
/* компилятор "знает"
допустимый тип передаваемого значения */
list.add("JavaFX 2");
String res = list.get(0); /* компилятор
"знает"
допустимый тип возвращаемого значения */
// list.add(new StringBuilder("C#")); // ошибка компиляции
// компилятор не позволит добавить "посторонний" тип
System.out.print(list); // удобный вывод
}
}
В результате будет выведено:
[Java, JavaFX 2]
В данной ситуации не создается новый класс для каждого конкретного типа
и сама коллекция не меняется, просто компилятор снабжается информацией
о типе элементов, которые могут храниться в list. При этом параметром коллек-
ции может быть только объектный тип.
КОЛЛЕКЦИИ
257
Следует отметить, что указывать тип следует при создании ссылки, иначе
будет позволено добавлять объекты всех типов. На этом основан принцип ти-
побезопасности, обеспечиваемый параметризацией коллекций. Пусть в систе-
ме онлайн-торговли используются понятия Order и Item. Заказы могут соби-
раться в списки, например, список заказов за день. Возможна ситуация, когда
по каким-либо причинам в список заказов будет добавлен экземпляр товара.
В этой ситуации даже свести баланс действительно сделанных заказов про-
стым определением размера списка будет невозможно. Придется извлекать
каждый объект, определять его тип и проч.
/* # 2 # некорректная(raw) и корректная коллекция # UncheckCheckRun.java */
package
by.bsu.collection;
import
java.util.ArrayList;
public
class UncheckCheckRun {
public
static void main(String[ ] args) {
ArrayList raw = new ArrayList() { // "сырая" коллекция – raw type
{
// логический блок анонимного класса
add(new Order(231, 12.f));
add(new Item(23154, 120.f, "Xerox"));
add(new Order(217, 1.7f));
}
};
// при извлечении требуется приведение типов
Order or1 = (Order) raw.get(0);
Item or2 = (Item) raw.get(1);
Order or3 = (Order) raw.get(2);
for (Object ob : raw) {
System.out.println("raw " + ob);
}
ArrayList orders = new ArrayList() {
{
add(new Order(231, 12.f));
add(new Order(389, 2.9f));
add(new Order(217, 1.7f));
// add(new Item(23154, 120.f, "Xerox"));
// ошибка компиляции: список параметризован
}
};
for (Order ob : orders) {
System.out.println("Order: " + ob);
}
}
}
В результате будет выведено:
raw Order [idOrder=231, amount=12.0]
raw Item [idItem=231, price=12.0, name=Xerox]
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
258
raw Order [idOrder=217, amount=1.7]
Order: Order [idOrder=231, amount=12.0]
Order: Order [idOrder=389, amount=2.9]
Order: Order [idOrder=217, amount=1.7]
где классы Order и Item представляют собой сущности Заказ и Товар в следу-
ющем виде:
/* # 3 # класс используется здесь и далее для наполнения коллекций # Order.java */
package
by.bsu.collection;
public
class Order {
private
int orderId;
private
float amount;
// поля и методы описания подробностей заказа
public
Order( int orderId, float amount) {
this.orderId = orderId;
this.amount = amount;
}
public
int getOrderId () {
return orderId;
}
public
float getAmount() {
return amount;
}
@Override
public
String toString() {
return "Order [orderId =" + orderId + ", amount=" + amount + "]";
}
}
/* # 4 # класс используется здесь и далее для наполнения коллекций # Item.java */
package
by.bsu.collection;
public
class Item {
private
int itemId;
private
float price;
private
String name;
public
Item( int itemId, float price, String name) {
this itemId = itemId;
this.price = price;
this.name = name;
}
public
int getItemId () {
return itemId;
}
public
float getPrice() {
return price;
}
public
String getName() {
КОЛЛЕКЦИИ
259
return name;
}
@Override
public
String toString() {
return "Item [itemId =" + itemId + ", price=" + price + ", name=" + name + "]\n";
}
}
Чтобы параметризация коллекции была полной, необходимо указывать па-
раметр и при объявлении ссылки, и при создании объекта.
Интерфейс Iterator используется для последовательного доступа к элемен-
там коллекции. Ниже приведен пример поиска с помощью итератора заказов,
сумма которых превышает заданную.
/* # 5 # методы класса ArrayList и интерфейса Iterator # RunIterator.java # FindOrder.java*/
package
by.bsu.collection;
import
java.util.Iterator;
import
java.util.List;
import
java.util.ArrayList;
public
class RunIterator {
public
static void main(String[ ] args) {
ArrayList orders = new ArrayList() {
{
add(new Order(231, 12.f));
add(new Order(389, 2.9f));
add(new Order(217, 1.7f));
}
};
FindOrder fo = new FindOrder();
List res = fo.findBiggerAmountOrder(10f, orders);
System.out.println(res);
}
}
package
by.bsu.collection;
import
java.util.List;
import
java.util.Iterator;
public
class FindOrder {
public
List findBiggerAmountOrder(float bigAmount, List orders) {
ArrayList bigPrices = new ArrayList();
Iterator it = orders.iterator();
while (it.hasNext()) {
Order current = it.next();
if(current.getAmount() >= bigAmount) {
bigPrices.add(current);
}
}
return
bigPrices;
}
}
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
260
В результате на консоль будет выведено:
[Order [idOrder=231, amount=12.0]]
Метасимвол в коллекциях
Метасимволы используются при параметризации коллекций для расшире-
ния возможностей самой коллекции и обеспечения ее типобезопасности.
Например, если параметр метода предыдущего примера изменить с List
на List extends Order>, то в метод можно будет передавать коллекции, пара-
метризованные любым допустимым типом, а именно классом Order и любым
его подклассом, что невозможно при записи без анонимного символа.
Но в методе нельзя будет добавить к коллекции новый элемент, пусть даже
и допустимого типа, так как компилятору неизвестен заранее тип параметриза-
ции списка.
List findBiggerAmountOrder(float bigAmount, List extends Order> orders) {
// orders.add(new Order(231, 12.f)); // ошибка компиляции
orders.remove(0);
// удалять можно
// some code here
}
Объясняется это тем, что список, передаваемый в качестве параметра мето-
да, может быть параметризован классом DiscountOrder, а в методе будет со-
вершаться попытка добавления экземпляра самого класса Order, как показано
выше, что недопустимо определением самой параметризации передаваемого
в метод списка, а именно:
findBiggerAmountOrder(10.f, new ArrayList());
где
public
class DiscountOrder extends Order {
// поля
public
DiscountOrder(int idOrder, float amount) {
super(idOrder, amount);
}
// методы
}
Поэтому добавление к спискам, параметризованным метасимволом с при-
менением extends, запрещено всегда.
Параметризация List super Order> утверждает, что параметр метода или
возвращаемое значение может получить список типа Order или любого из его
суперклассов, в то же время разрешает добавлять туда экземпляры класса
Order и любых его подклассов.
List findBiggerAmountOrder(float bigAmount, List super Order> orders) {
orders.add(new Order(231, 12.f));
//
some code here
}
КОЛЛЕКЦИИ
261
Или, если использовать super для определения типа возвращаемого значения:
List super Order> findBiggerAmountOrder( float bigAmount, List extends Order> orders) {
// some code here
}
В этом случае к списку, возвращенному методом, можно будет добавлять
экземпляры класса Order и его подклассов.
Интерфейс ListIterator
Для доступа к элементам списка может также использоваться интерфейс
ListIterator, который позволяет получить доступ сразу в необходимую про-
граммисту позицию списка вызовом метода listIterator(int index). Интерфейс
ListIterator расширяет интерфейс Iterator и предназначен для обработ-
ки списков и их вариаций. Наличие методов E previous(), int previousIndex()
и boolean hasPrevious() обеспечивает обратную навигацию по списку. Метод
int nextIndex() возвращает номер следующего итератора. Метод void add(E obj)
позволяет вставлять элемент в список текущей позиции. Вызов метода void
set(E obj) производит замену текущего элемента списка на объект, передавае-
мый методу в качестве параметра.
При внесении изменений в коллекцию после извлечения итератора гаранти-
рует генерацию исключения java.util.ConcurrentModificationException при
попытке использовать итератор позднее.
ArrayList orders = new ArrayList<>();
// добавление элементов
Iterator it = orders.iterator();
orders.add(new Order(12, 12.1f)); // или remove(0);
while
(it.hasNext()) {
System.out.println(it.next());
}
Параллельная или конкурентная модификация коллекции различными актив-
ностями (самой коллекцией и ее итератором) приводит к неразрешимой пробле-
ме и заканчивается генерацией исключения практически всегда. Проблема за-
ключается в том, что итератор извлек N число позиций, а коллекция изменила
число своих экземпляров и итератор перестал соответствовать коллекции. Если
модификацию коллекции и работу с итератором нужно выполнять из различных
потоков, то для решения такой задачи используются ограниченно потокобез-
опасные решения для коллекций из пакета java.util.concurrent.
При создании класса, содержащего коллекцию элементов, возможны два
способа: агрегация коллекции в качестве поля класса или отношение HAS-A
и наследование от класса, представляющего коллекцию или отношение IS-A.
Последнее встречается значительно реже.
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
262
/* # 6 # класс, агрегирующий список, с реализацией интерфейса Iterable # Order.java*/
import
java.util.Iterator;
import
java.util.List;
import
java.util.Collections;
public
class Order implements Iterable - {
private
int orderId;
private
List- listItems;
// private float amount; // не нужен, т.к. сумму можно вычислить
public
Order(int orderId, List- listItems) {
this.orderId = orderId;
this.listItems = listItems;
}
public
int getOrderId () {
return orderId;
}
public
List- getListItems() {
return Collections.unmodifiableList(listItems);
}
// некоторые делегированные методы интерфейсов List и Collection
Достарыңызбен бөлісу: |