- c) {
super(c);
}
public
Order(int orderId, ArrayList extends Item> c) {
super(c);
КОЛЛЕКЦИИ
263
this.orderId = orderId;
}
public
int getOrderId() {
return orderId;
}
public
void setOrderId (int orderId) {
this. orderId = orderId;
}
}
При такой реализации нет явной необходимости в переопределении метода класса
ArrayList, так как можно просто воспользоваться его стандартными методами.
Интерфейс Comparator
Реализация метода equals() класса Object предоставляет возможность про-
верить, эквивалентен один экземпляр другому или нет. На практике возникает
необходимость сравнения объектов на больше/меньше либо равно.
При реализации интерфейса java.util.Comparator появляется возможность
сортировки списка объектов конкретного типа по правилам, определенным для это-
го типа. Контракт интерфейса подразумевает реализацию метода int compare(T ob1,
T ob2), принимающего в качестве параметров два объекта, для которых должно
быть определено возвращаемое целое значение, знак которого и определяет прави-
ло сортировки. Метод public abstract boolean equals(Object obj), также объявлен-
ный в интерфейсе Comparator, очень рекомендуется переопределять, если
экземпляр класса будет использоваться для хранения информации. Это необходимо
для исключения противоречивой ситуации, когда для двух объектов метод
compare() возвращает 0, т. е. сообщает об их эквивалентности, в то же время метод
equals() для этих же объектов возвращает false, так как данный метод не был никем
определен и была использована его версия из класса Object. Кроме того, нали-
чие метода equals() обеспечивает корректную работу метода семантического по-
иска и проверки на идентичность contains(Object o), определенного в интерфей-
се java.util.Collection, а следовательно, реализованного в любой коллекции.
Метод compare() автоматически вызывается при сортировке списка мето-
дом: static void sort(List list, Comparator super T> c) класса Collections,
в качестве первого параметра принимающий коллекцию, в качестве второго —
объект-comparator, из которого извлекается и применяется правило сортировки.
С помощью анонимного типа можно привести простейшую реализацию ком-
паратора.
/* # 8 # сортировка списка по полю, определенному в классе comparator-е #
SortItemRunner.java */
package
by.bsu.comparison;
import
java.util.ArrayList;
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
264
import
java.util.Collections;
import
java.util.Comparator;
import
by.bsu.collection.Item;
public
class SortItemRunner {
public
static void main(String[ ] args) {
ArrayList- p = new ArrayList
- () {
{
add(new Item(52201, 9.75f, "T-Shirt"));
add(new Item(52127, 13.99f, "Dress"));
add(new Item(47063, 45.95f, "Jeans"));
add(new Item(90428, 60.9f, "Gloves"));
add(new Item(53295, 31f, "Shirt"));
add(new Item(63220, 14.9f, "Tie"));
}
};
// создание компаратора
Comparator- comp = new Comparator
- () {
// сравнение для сортировки по убыванию цены товара
public int compare(Item one, Item two) {
return Double.compare(two.getPrice(), one.getPrice());
}
// public boolean equals(Object ob) { /* реализация */ }
};
// сортировка списка объектов
Collections.sort(p, comp);
System.out.println(p);
}
}
В результате будет выведено:
[Item [itemId=90428, price=60.9, name=Gloves]
, Item [itemId =47063, price=45.95, name=Jeans]
, Item [itemId =53295, price=31.0, name=Shirt]
, Item [itemId =63220, price=14.9, name=Tie]
, Item [itemId =52127, price=13.99, name=Dress]
, Item [itemId =52201, price=9.75, name=T-Shirt]
]
Если в процессе использования необходимы сортировки по различным по-
лям класса, то реализацию компаратора следует вынести в отдельный класс.
Для обеспечения возможности сортировки по любому полю класса Item
следует создать перечисление со значениями, соответствующими полям этого
класса
public
enum ItemEnum {
ITEM_ID, PRICE, NAME
}
и отдельный класс, реализующий параметризованный интерфейс Comparator.
КОЛЛЕКЦИИ
265
/* # 9 # возможность сортировки по всем полям класса # ItemComparator.java */
package
by.bsu.collection;
import
java.util.Comparator;
public
class ItemComparator implements Comparator- {
private
ItemEnum sortingIndex;
public
ItemComparator(ItemEnum sortingIndex) {
setSortingIndex(sortingIndex);
}
public
final void setSortingIndex(ItemEnum sortingIndex) {
if (sortingIndex == null) {
throw new IllegalArgumentException();
}
this
.sortingIndex = sortingIndex;
}
public
ItemEnum getSortingIndex() {
return sortingIndex;
}
@Override
public
int compare(Item one, Item two) {
switch (sortingIndex) {
case ITEM_ID:
return one.getItemId() - two.getItemId();
case PRICE:
return Double.compare(two.getPrice() - one.getPrice());
case NAME:
return one.getName().compareTo(two.getName());
default:
throw new EnumConstantNotPresentException(ItemEnum.class,sortingIndex.name());
}
}
}
При необходимости сортировки по полю itemId следует изменить значение
статической переменной sortingIndex и в качестве второго параметра методу
sort() передать объект класса ItemComparator:
Collections.sort (p, new ItemComparator (ItemEnum.ITEM_ID));
Достаточно легко реализовать возможность сортировки по возрастанию
и убыванию для каждого из полей класса, добавив в перечисление, например,
поле ascend типа boolean, от значения которого поставить в зависимость знак
числа, возвращаемого методом compare(). Перечисление ItemEnum будет вы-
глядеть следующим образом:
/* # 10 # перечисление, предоставляющее возможность сортировки по убыванию\
возрастанию для всех полей класса # FullItemEnum.java # */
package
by.bsu.collection;
public
enum FullItemEnum {
ITEM_ID(true), PRICE(false), NAME(true);
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
266
private
boolean ascend;
private
FullItemEnum(
boolean ascend) {
this.ascend = ascend;
}
public
void setAscend(
boolean ascend) {
this.ascend = ascend;
}
public
boolean getAscend() {
return ascend;
}
}
Класс
ItemComparator также следует переписать, так как с учетом новых воз-
можностей
число кодов блоков case увеличится.
Класс-компаратор, являясь логической частью класса-сущности, может
быть его частью и представлен в виде статического внутреннего класса
/* # 11 # класс-сущность с внутренними классами-компараторами # Item.java # */
package
by.bsu.collection;
import
java.util.Comparator;
public
class Item {
private
int itemId;
private
float price;
private
String name;
public
Item(
int itemId,
float price, String name) {
super();
this.itemId = itemId;
this.price = price;
this.name = name;
}
public
int getItemId() {
return itemId;
}
public
float getPrice() {
return price;
}
public
String getName() {
return name;
}
public
static class IdComparator
implements Comparator
- {
@Override
public int compare(Item one, Item two) {
return one.getItemId() - two.getItemId();
}
}
public
static class PriceComparator implements Comparator- {
@Override
public int compare(Item one, Item two) {
КОЛЛЕКЦИИ
267
return Double.compare(two.getPrice(), one.getPrice());
}
}
}
Экземпляр компаратора создается обычным для внутренних классов способом
Item.IdComparator comp = new Item.IdComparator();
Объявление внутри класса позволяет манипулировать доступом к его функ-
циональности.
Интерфейс Comparator не рекомендуется имплементировать классу-сущ-
ности, для сортировки экземпляров которого он предназначается.
Класс LinkedList и интерфейс Queue
Коллекция LinkedList реализует связанный список.
java.util.AbstractCollection
java.util.AbstractList
java.util.AbstractSequentialList
java.util.LinkedList
Реализует кроме интерфейсов, указанных при описании ArrayList, также
интерфейсы Queue и Deque.
Связанный список хранит ссылки на объекты отдельно вместе со ссылками
на следующее и предыдущее звенья последовательности, поэтому часто назы-
вается двунаправленным списком. Операции добавления и удаления выполня-
ются достаточно быстро, в отличие от операций поиска и навигации.
В этом классе объявлены методы, позволяющие манипулировать им как
очередью, двунаправленной очередью и т. д. Двунаправленный список кроме
обычного имеет особый «нисходящий» итератор, позволяющий двигаться
от конца списка к началу, и извлекается методом descendingIterator().
Для манипуляций с первым и последним элементами списка в LinkedList
реализованы методы:
void addFirst(E ob), void addLast(E ob) — добавляющие элементы в нача-
ло и конец списка;
E
getFirst(), E getLast() — извлекающие элементы;
E
removeFirst(), E removeLast() — удаляющие и извлекающие элементы;
E
removeLastOccurrence(E elem), E removeFirstOccurrence(E elem) —
удаляющие и извлекающие элемент, первый или последний раз встречаемый
в списке.
Класс LinkedList реализует интерфейс Queue, что позволяет спи-
ску придать свойства очереди. В компьютерных науках очередь — структура
данных, в основе которой лежит принцип FIFO (first in, first out). Элементы добав-
ляются в конец и вынимаются из начала очереди. Но существует возможность
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
268
не только добавлять и удалять элементы, также можно просмотреть, что нахо-
дится в очереди.
К тому же специализированные методы интерфейса
Queue
по манипуляции первым и последним элементами такого списка
E element(),
boolean offer(E o), E peek(), E poll(), E remove() работают немного быстрее,
чем соответствующие методы класса
LinkedList.
Методы интерфейса
Queue:
boolean add(E o) — вставляет элемент в очередь, если же очередь полно-
стью заполнена, то генерирует исключение
IllegalStateException;
boolean offer(E o) — вставляет элемент в очередь,
если возможно;
E
element() — возвращает, но не удаляет головной элемент очереди;
E
peek() — возвращает, но не удаляет головной элемент очереди, возвраща-
ет
null, если очередь пуста;
E
poll() — возвращает и удаляет головной элемент очереди, возвращает
null, если очередь пуста;
E
remove() — возвращает и удаляет головной элемент очереди.
Методы
element() и
remove() отличаются от методов
peek() и
poll() тем, что
генерируют исключение
NoSuchElementException, если очередь пуста.
Queue
- queue = new LinkedList<>();
Создается очередь простым присваиванием связанного списка ссылке типа
Queue.
/* # 12 # двунаправленный список и очередь # OrderQueueAction.java */
package
by.bsu.collection;
import
java.util.LinkedList;
import
java.util.Queue;
public
class OrderQueueAction {
public
static void main(String[ ] args) {
LinkedList orders = new LinkedList() {
{
add(new Order(231, 12.f));
add(new Order(389, 2.9f));
add(new Order(217, 1.7f));
}
};
Queue queueA = orders; // создание очереди
queueA.offer(new Order(222, 9.7f)); // элемент не добавится
orderProcessing(queueA); // обработка очереди
if (queueA.isEmpty()) {
System.out.println("Queue of Orders is empty");
}
}
public
static void orderProcessing(Queue queue) { // заменить void -> boolean
Order ob = null;
// заменить while -> do{} while
while ((ob = queue.poll()) != null) { // извлечение с удалением
КОЛЛЕКЦИИ
269
System.out.println("Order #" + ob.getIdOrder() + " is processing");
// verifying and processing
}
}
}
В результате будет выведено:
Order #231 is processing
Order #389 is processing
Order #217 is processing
Queue of Orders is empty
При всей схожести списков ArrayList и LinkedList существуют серьезные
отличия, которые необходимо учитывать при использовании коллекций в кон-
кретных задачах. Если необходимо осуществлять быструю навигацию по спи-
ску, то следует применять ArrayList, так как перебор элементов в LinkedList
осуществляется на порядок медленнее. С другой стороны, если требуется ча-
сто добавлять и удалять элементы из списка, то уже класс LinkedList обеспе-
чивает значительно более высокую скорость переиндексации. То есть если кол-
лекция формируется в начале процесса и в дальнейшем используется только
для доступа к информации, то применяется ArrayList, если же коллекция под-
вергается изменениям на всем протяжении функционирования приложения,
то выгоднее LinkedList.
Интерфейс Queue также реализует класс PriorityQueue.
java.util.AbstractCollection
java.util.AbstractQueue
java.util.PriorityQueue
В такую очередь элементы добавляются не в порядке «живой очереди»,
а в порядке, определяемом в классе, экземпляры которого содержатся в очере-
ди. Сам порядок следования задан реализацией интерфейсов Comparator
или Comparable.
По умолчанию компаратор размещает элементы в очереди в порядке воз-
растания. Если порядок в классе не определен, а именно, не реализован
ни один из указанных интерфейсов, то генерируется исключительная ситуация
ClassCastException при добавлении второго элемента в очередь. При добавле-
нии первого элемента компаратор не вызывается.
PriorityQueue orders = new PriorityQueue();
orders.add(new Order(546, 53.f)); // нормально
orders.add(new Order(146, 13.f)); // ошибка времени выполнения
С другой стороны класс String реализует интерфейс Comparable:
PriorityQueue q = new PriorityQueue();
orders.add(new String("Oracle")); // нормально
orders.add(new String("Google")); // нормально
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
270
Если попытаться заменить тип
String на
StringBuilder или
StringBuffer,
то создать очередь
PriorityQueue так просто не удастся, как и в случае добав-
ления объектов класса
Order. Решением такой задачи будет создание нового
класса-оболочки с полем типа
StringBuilder или
Order и реализацией интер-
фейса
Comparable.
/* # 13 # пользовательский класс, объект которого может быть добавлен в очередь
PriorityQueue и множество TreeSet # Order.java */
package
by.bsu.collection;
public
class Order
implements Comparable
{
private
int orderId;
private
float amount;
// поля и методы описания подробностей заказа
public
Order(int orderId, float amount) {
super();
this.orderId = orderId;
this.amount = amount;
}
public
int getOrderId() {
return orderId;
}
public
float getAmount() {
return amount;
}
// реализация метода интерфейса Comparable
@Override
public
int compareTo(Order ob) {
return this.orderId - ob.orderId;
}
@Override
public
String toString() {
return "Order [orderId =" + orderId + ", amount=" + amount + "]";
}
}
Предлагаемое решение универсально для любых пользовательских типов.
Предложенное решение применимо для создания упорядоченных множеств
вида TreeSet, которые используют компаратор для сортировки при добавлении
экземпляра в множество.
Интерфейс Deque и класс ArrayDeque
Интерфейс Deque определяет «двунаправленную» очередь и, соответственно,
методы доступа к первому и последнему элементам двусторонней очереди.
Реализацию этого интерфейса можно использовать для моделирования стека.
Методы обеспечивают удаление, вставку и обработку элементов. Каждый из этих
КОЛЛЕКЦИИ
271
методов существует в двух формах. Одни методы создают исключительную ситу-
ацию в случае неудачного завершения, другие возвращают какое-либо из значений
(null или false в зависимости от типа операции). Вторая форма добавления элемен-
тов в очередь сделана специально для реализаций Deque, имеющих ограничение
по размеру. В большинстве реализаций операции добавления заканчиваются
успешно. Методы addFirst(), addLast() вставляют элементы в начало и в конец
очереди соответственно. Метод add() унаследован от интерфейса Queue и абсо-
лютно аналогичен методу addLast() интерфейса Deque. Объявить двуконечную
очередь на основе связанного списка можно, например, следующим образом:
Deque dq = new LinkedList<>();
Класс ArrayDeque быстрее, чем Stack, если используется как стек, и быст-
рее, чем LinkedList, если используется в качестве очереди.
Множества
Интерфейс Set объявляет поведение коллекции, не допускающей ду-
блирования элементов. Интерфейс SortedSet наследует Set и объявля-
ет поведение набора, отсортированного в возрастающем порядке, заранее
определенном для класса. Интерфейс NavigableSet существенно облегча-
ет поиск элементов, например, расположенных рядом с заданным.
Класс HashSet наследуется от абстрактного суперкласса AbstractSet
и реализует интерфейс Set, используя хэш-таблицу для хранения коллек-
ции.
java.util.AbstractCollection
java.util.AbstractSet
java.util.HashSet
Ключ (хэш-код) используется в качестве индекса хэш-таблицы для доступа
к объектам множества, что значительно ускоряет процессы поиска, добавления
и извлечения элемента. Скорость указанных процессов становится заметной
для коллекций с большим количеством элементов. Множество HashSet не яв-
ляется сортированным. В таком множестве могут храниться элементы с одина-
ковыми хэш-кодами в случае, если эти элементы не эквивалентны при сравне-
нии. Для грамотной организации HashSet следует следить, чтобы реализации
методов hashCode() и equals() соответствовали контракту.
Конструкторы класса:
HashSet()
HashSet(Collection extends E> c)
HashSet(int capacity)
HashSet(int capacity, float loadFactor),
где capacity — число ячеек для хранения хэш-кодов.
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
272
/* # 14 # использование множества для вывода всех уникальных слов из файла #
DemoHashSet.java */
package
by.bsu.set;
import
java.io.*;
import
java.util.*;
public
class DemoHashSet {
public
static void main(String[ ] args) {
HashSet words = new HashSet<>(100);
long callTime = System.
nanoTime();
Scanner scan =
null;
try {
scan =
new Scanner(
new File("texts\\nabokov.txt"));
scan.useDelimiter("[^А-я]+");
while (scan.hasNext()) {
String
word
=
scan.next();
words.add(word.toLowerCase());
}
}
catch
(FileNotFoundException e) {
e.printStackTrace();
}
Iterator
it = words.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
TreeSet ts = new TreeSet<>(words);
System.out.println(ts);
long totalTime = System.nanoTime() - callTime;
System.out.println("различных слов: " + words.size() + ", "
+ totalTime + " наносекунд");
}
}
Класс TreeSet для хранения объектов использует бинарное дерево.
java.util.AbstractCollection
java.util.AbstractSet
java.util.TreeSet
При добавлении объекта в дерево он сразу же размещается в необходимую пози-
цию с учетом сортировки. Сортировка происходит благодаря тому, что класс реали-
зует интерфейс SortedSet, где правило сортировки добавляемых элементов опреде-
ляется в самом классе, сохраняемом в множестве, который в большинстве случаев
реализует интерфейс Comparable. Обработка операций удаления и вставки объек-
тов происходит медленнее, чем в хэш-множествах, но быстрее, чем в списках.
Конструкторы класса:
TreeSet()
TreeSet(Collection extends E> c)
TreeSet(Comparator super E> c)
TreeSet(SortedSet s)
КОЛЛЕКЦИИ
273
Класс TreeSet содержит методы по извлечению первого и последнего
(наименьшего и наибольшего) элементов E first() и E last(). Методы subSet(E from,
E
to), tailSet(E from) и headSet(E to) предназначены для извлечения определенной
части множества. Метод Comparator super E> comparator() возвращает объ-
ект Comparator, используемый для сортировки объектов множества или null,
если выполняется обычная сортировка.
/* # 15 # создание множества из списка и его методы # DemoTreeSet.java */
package
by.bsu.set;
import
java.util.*;
public
class DemoTreeSet {
public
static void main(String[ ] args) {
ArrayList list = new ArrayList<>();
boolean
b;
for
(int i = 0; i < 6; i++) {
list.add((int) (Math.random() * 10) + "Y ");
}
System.out.println(list + "список");
TreeSet set = new TreeSet(list);
System.out.println(set + "множество");
System.out.println(set.comparator()); // null – String реализует Comparable
// извлечение наибольшего и наименьшего элементов
System.out.println(set.last() + " " + set.first());
HashSet hSet = new HashSet<>(set);
for (String str : hSet) {
System.out.println(str + " " + str.hashCode());
}
}
}
В результате может быть выведено:
[6Y , 5Y , 2Y , 5Y , 4Y , 8Y ]список
[2Y , 4Y , 5Y , 6Y , 8Y ]множество
null
8Y 2Y
2Y 50841
6Y 54685
8Y 56607
4Y 52763
5Y 53724
Множество инициализируется списком и сортируется сразу же в процессе со-
здания. С помощью итератора элемент может быть найден и удален из множества.
Для множества, состоящего из обычных строк, используется лексикографическая
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
274
сортировка, задаваемая реализацией интерфейса Comparable, поэтому метод
comparator() возвращает null.
Абстрактный класс EnumSet> наследуется от аб-
страктного класса AbstractSet.
java.util.AbstractCollection
java.util.AbstractSet
java.util.EnumSet
Класс специально реализован для работы с типами enum. Все элементы та-
кой коллекции должны принадлежать единственному типу enum, определенно-
му явно или неявно. Внутренне множество представимо в виде вектора битов,
обычно единственного long. Множества нумераторов поддерживают перебор
по диапазону из нумераторов. Скорость выполнения операций над таким множе-
ством очень высока, даже если в ней участвует большое количество элементов.
Создать объект этого класса можно только с помощью статических методов.
Метод EnumSet noneOf(Class elemType) cоздает пустое множество
нумерованых констант с указанным типом элемента, метод allOf(Class
elementType) создает множество нумерованых констант, содержащее все
элементы указанного типа. Метод of(E first, E… rest) создает множество,
первоначально содержащее указанные элементы. С помощью метода
complementOf(EnumSet s) создается множество, содержащее все элемен-
ты, которые отсутствуют в указанном множестве. Метод range(E from, E to)
создает множество из элементов, содержащихся в диапазоне, определенном
двумя элементами. При передаче указанным методам в качестве параметра null
будет сгенерирована исключительная ситуация NullPointerException.
/* # 16 # иcпользование множества enum-типов # CarManufacturer.java #
UseEnumSet.java */
package
by.bsu.set;
public
enum CarManufacturer {
AUDI, BMW, VW, TOYOTA, HONDA, ISUZU, SUZUKI, VOLVO, RENAULT
}
package
by.bsu.set;
import
java.util.EnumSet;
public
class UseEnumSet {
public
static void main(String[ ] args) {
/* множество japanAuto содержит элементы типа
enum из интервала, определенного двумя элементами */
EnumSet japanAuto =
EnumSet.range(CarManufacturer.TOYOTA, CarManufacturer.SUZUKI);
/* множество other будет содержать все элементы, не содержащиеся в множестве japanAuto */
EnumSet other = EnumSet.complementOf(japanAuto);
System.out.println(japanAuto);
System.out.println(other);
action("audi", japanAuto);
КОЛЛЕКЦИИ
275
action("suzuki", japanAuto);
}
public
static boolean action(String auto, EnumSet
set) {
CarManufacturer cm = CarManufacturer.valueOf(auto.toUpperCase());
boolean
ok = false;
if
(ok = set.contains(cm)) {
// обработка
System.out.println("Обработан: " + cm);
}
else
{
System.out.println("Обработка невозможна: " + cm);
}
return ok;
}
}
В результате будет выведено:
[TOYOTA, HONDA, ISUZU, SUZUKI]
[AUDI, BMW, VW, VOLVO, RENAULT]
Обработка невозможна: AUDI
Обработан: SUZUKI
Карты отображений
Карта отображений — это объект, который хранит пару «ключ–значение».
Поиск объекта (значения) облегчается по сравнению с множествами за счет
того, что его можно найти по его уникальному ключу. Уникальность объектов-
ключей должна обеспечиваться переопределением методов hashCode()
и equals() или реализацией интерфейсов Comparable, Comparator пользова-
тельским классом. Классы карт отображений:
AbstractMap — реализует интерфейс Map, является супер-
классом для всех перечисленных карт отображений;
HashMap — использует хэш-таблицу для работы с ключами;
TreeMap — использует дерево, где ключи расположены в виде дере-
ва поиска в определенном порядке;
WeakHashMap — позволяет механизму сборки мусора удалять
из карты значения по ключу, ссылка на который вышла из области видимости
приложения;
LinkedHashMap — образует дважды связанный список ключей.
Этот механизм эффективен, только если превышен коэффициент загруженно-
сти карты при работе с кэш-памятью и др.
Для класса IdentityHashMap хэш-коды объектов-ключей вычи-
сляются методом System.identityHashCode() по адресу объекта в памяти
в отличие от обычного значения hashCode(), вычисляемого сугубо по содер-
жимому самого объекта.
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
276
Интерфейсы карт:
Map — отображает уникальные ключи и значения;
Map.Entry — описывает пару «ключ–значение»;
SortedMap — содержит отсортированные ключи и значения;
NavigableMap — добавляет новые возможности навигации и пои-
ска по ключу.
Интерфейс
Map содержит следующие методы:
V
get(Object obj) — возвращает значение, связанное с ключом
obj. Если эле-
мент с указанным ключом отсутствует в карте, то возвращается значение
null;
V
put(K key, V value) — помещает ключ
key и значение
value в вызываю-
щую карту. При добавлении в карту элемента с существующим ключом прои-
зойдет замена текущего элемента новым. При этом метод возвратит заменяе-
мый элемент;
void putAll(Map extends K,? extends V> t) — помещает коллекцию
t
в вызывающую карту;
V
remove(Object key) — удаляет пару «ключ–значение» по ключу
key;
void clear() — удаляет все пары из вызываемой карты;
boolean containsKey(Object key) — возвращает
true, если вызывающая
карта содержит
key как ключ;
boolean containsValue(Object value) — возвращает
true, если вызывающая
карта содержит
value как значение;
Set keySet() — возвращает множество ключей;
Set> entrySet() — возвращает множество, содержащее значе-
ния карты в виде пар «ключ–значение»;
Collection values() — возвращает коллекцию, содержащую значения карты.
В коллекциях, возвращаемых тремя последними методами, можно только
удалять элементы, добавлять нельзя. Данное ограничение обуславливается па-
раметризацией возвращаемого методами значения.
Интерфейс
Map.Entry представляет пару «ключ–значение» и содер-
жит следующие методы:
K getKey() — возвращает ключ текущего входа;
V
getValue() — возвращает значение текущего входа;
V
setValue(V obj) — устанавливает
значение объекта obj в текущем входе.
В примере показаны способы создания хэш-карты и доступа к ее элементам.
/* # 17 # создание хэш-карты и замена элемента по ключу # DemoHashMap.java */
package
by.bsu.maps;
public
class DemoHashMap {
public
static void main(String[] args) {
HashMap
hm = new HashMap(3) {
{
this.put("Сырок", 3);
this.put("Пряник", 5);
КОЛЛЕКЦИИ
277
this
.put("Молоко", 1);
this
.put("Хлеб", 1);
}
};
System.out.println(hm);
hm.put("Пряник", 4); // замена или добавление при отсутствии ключа
System.out.println(hm + "после замены элемента");
Integer a = hm.get("Хлеб");
System.out.println(a + " - найден по ключу 'Хлеб'");
// вывод хэш-таблицы с помощью методов интерфейса Map.Entry
Set> setv = hm.entrySet();
System.out.println(setv);
Iterator> i = setv.iterator();
while
(i.hasNext()) {
Map.Entry me = i.next();
System.out.println(me.getKey() + " : " + me.getValue());
}
Set val = new HashSet(hm.values());
System.out.println(val);
}
}
{Пряник=5, Хлеб=1, Сырок=3, Молоко=1}
{Пряник=4, Хлеб=1, Сырок=3, Молоко=1}после замены элемента
1 - найден по ключу 'Хлеб'
[Пряник=4, Хлеб=1, Сырок=3, Молоко=1]
Пряник : 4
Хлеб : 1
Сырок : 3
Молоко : 1
[1, 3, 4]
Метод put() не проверяет наличия пары с таким же ключом, как и у добав-
ляемой пары и просто заменяет значение по ключу на новое. Если критично
наличие всех ранее добавленных элементов, следует перед добавлением пары
выполнять проверку на наличие идентичных ключей. В простейшем варианте
это выглядит:
HashMap hm = new HashMap() {
{
this
.put("Сырок", 3);
this
.put("Пряник", 5);
this
.put("Молоко", 1);
this
.put("Хлеб", 1);
}
};
if
( !hm.containsKey("Пряник") ) { // замена не произойдет, если ключ существует
hm.put("Пряник", 4);
}
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
278
Класс EnumMap, V> в качестве ключа может прини-
мать только объекты, принадлежащие одному типу enum, который должен
быть определен при создании коллекции. Специально организован для обеспе-
чения максимальной скорости доступа к элементам коллекции.
/* # 18 # пример работы с классом EnumMap # GASStation.java # Gases.java */
package
by.bsu.enummap;
enum
GASStation {
DT(50), A80(30), A92(30), A95(50), GAS(40);
private
Integer maxVolume;
private
GASStation (int maxVolume) {
this
.maxVolume = maxVolume;
}
public
Integer getMaxVolume() {
return
maxVolume;
}
}
import
java.util.EnumMap;
public
class Gases {
public
static void main(String[ ] args) {
EnumMap station1 =
new
EnumMap(GASStation.class);
station1.put(GASStation.DT, 10);
station1.put(GASStation.A80, 5);
station1.put(GASStation.A92, 30);
EnumMap station2 =
new
EnumMap(GASStation.class);
station2.put(GASStation.DT, 25);
station2.put(GASStation.A95, 25);
System.out.println(station1);
System.out.println(station2);
��}
}
В результате будет выведено:
{DT=10, A80=5, A92=30}
{DT=25, A95=25}
Унаследованные коллекции
В ряде распределенных приложений, например с использованием сервлетов,
до сих пор применяются коллекции, более медленные в обработке, но при этом
потокобезопасные (thread-safety), существовавшие в языке Java с момента его со-
здания, а именно карта Hashtable, список Vector и перечисление (ана-
лог итератора) Enumeration. Все они также были параметризованы, но сохра-
няют возможность одновременного доступа из конкурирующих потоков.
КОЛЛЕКЦИИ
279
Класс Hashtable реализует интерфейс Map,
java.util.Dictionary
java.util.Hashtable
обладает также несколькими специфичными по сравнению с другими коллек-
циями методами:
Enumeration elements() — возвращает перечисление для значений карты;
Enumeration keys() — возвращает перечисление для ключей карты.
/* # 19 # создание хэш-таблицы и поиск элемента по ключу # HashTableDemo.java */
package
by.bsu.legacy;
import
java.util.*;
public
class HashTableDemo {
public
static void main(String[ ] args) {
Hashtable ht = new Hashtable() {
{
for
(int i = 0; i < 5; i++) {
ht.put(i,
Math.atan(i));
}
}
};
Enumeration ek = ht.keys();
int
key;
while
(ek.hasMoreElements()) {
key
=
ek.nextElement();
System.out.printf("%4d ", key);
}
System.out.println("");
Enumeration ev = ht.elements();
double
value;
while
(ev.hasMoreElements()) {
value
=
ev.nextElement();
System.out.printf("%.2f ", value);
}
}
}
В результате в консоль будет выведено:
4 3 2 1 0
1,33 1,25 1,11 0,79 0,00
Принципы работы с коллекциями, в отличие от их структуры, со сменой
версий языка существенно не изменились.
Класс Properties предназначен для хранения карты свойств, где и ключ,
и значения являются экземплярами класса String. Значения пары можно загру-
жать и хранить в файле.
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
280
/* # 20 # создание экземпляра и файла properties # PropertiesDemoWriter.java */
package
by.bsu.collection;
import
java.io.File;
import
java.io.FileWriter;
import
java.io.IOException;
import
java.util.Properties;
public
class PropertiesDemoWriter {
public static void main(String[ ] args) {
Properties props = new Properties();
try {
// установка значений экземпляру
props.setProperty("db.driver", "com.mysql.jdbc.Driver");
// props.setProperty("db.url", "jdbc:mysql://127.0.0.1:3306/testphones");
props.setProperty("db.user", "root");
props.setProperty("db.password", "pass");
props.setProperty("db.poolsize", "5");
// запись properties-файла в папку prop проекта
props.store(new FileWriter("prop" + File.separator + "database.properties"),null);
} catch (IOException e) {
e.printStackTrace();
}
}
}
В результате в файле database.properties будет размещена следующая ин-
формация:
#Wed Aug 01 03:40:01 FET 2012
db.password=pass
db.user=root
db.poolsize=5
db.driver=com.mysql.jdbc.Driver
Символ «=» служит по умолчанию разделителем ключа и значения в фай-
ле.properties, также в этом качестве можно использовать символ «:». Эти два
специальных символа при записи в файл в качестве части ключа или значе-
ния получают впереди символ «\», чтобы в дальнейшем при чтении не быть
воспринятым как разделитель «ключа–значения». Например, при задании
свойства вида
props.setProperty("db.url", "jdbc:mysql://127.0.0.1:3306/testphones");
в файл будет записано
db.url=jdbc\:mysql\://127.0.0.1\:3306/testphones
Извлечь информацию из файла достаточно просто.
КОЛЛЕКЦИИ
281
/* # 21 # загрузка файла properties в экземпляр и доступ к содержимому #
PropertiesDemo.java */
package
by.bsu.collection;
import
java.io.FileReader;
import
java.io.IOException;
import
java.util.Properties;
public
class PropertiesDemo {
public
static void main(String[ ] args) {
Properties props =
new Properties();
try
{
// загрузка пар ключ-значение через поток ввода-вывода
props.load(new FileReader("prop\\database.properties"));
}
catch
(IOException e) {
e.printStackTrace();
}
String driver = props.getProperty("db.driver");
// следующих двух ключей в файле нет
String maxIdle = props.getProperty("db.maxIdle");
// будет присвоен null
// значение "
20"
будет присвоено ключу, если он не будет найден в файле
String maxActive = props.getProperty("db.maxActive", "20");
System.
out.println(driver);
System.
out.println(maxIdle);
System.
out.println(maxActive);
}
}
В результате будет выведено:
com.mysql.jdbc.Driver
null
20
Алгоритмы класса Collections
Класс
java.util.Collections содержит большое количество статических мето-
дов, предназначенных для манипулирования коллекциями.
С применением предыдущих версий языка было разработано множество
коллекций, в которых никаких проверок нет, следовательно, при их использо-
вании нельзя гарантировать, что в коллекцию не будет помещен «посторон-
ний» объект. Для этого в класс
Collections был добавлен новый метод:
static
Collection checkedCollection(Collection c, Class type)
Этот метод создает коллекцию, проверяемую на этапе выполнения,
т. е. в случае добавления «постороннего» объекта генерируется исключение
ClassCastException:
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
282
/* # 22 # проверяемая коллекция # SafeSetRun.java */
package
by.bsu.check;
import
java.util.*;
public
class SafeSetRun {
public
static void main(String args[ ]) {
HashSet
orders;
//
orders = new HashSet(); // заменяемый код на jdk1.4 и ниже
orders = Collections.checkedSet(new HashSet(), Order.class);
orders.add(
new Order(
параметры));
// some code here
orders.add(
new Item(
параметры));
// runtime error
}
}
В этот же класс добавлен целый ряд статических методов, специализиро-
ванных для проверки конкретных типов коллекций, а именно:
checkedList(),
checkedSortedMap(), checkedMap(), checkedSortedSet(), checkedCollection(),
а также
void copy(List super T> dest, List extends T> src) — копирует все
элементы из одного списка в другой;
boolean disjoint(Collection> c1, Collection> c2) — возвращает
true,
если коллекции не содержат одинаковых элементов;
List emptyList(), Map emptyMap(), Set
emptySet() — возвращают пустой список, карту отображения и множество со-
ответственно;
void fill(List super T> list, T obj) — заполняет список заданным эле-
ментом;
int frequency(Collection> c, Object o) — возвращает количество вхожде-
ний в коллекцию заданного элемента;
boolean replaceAll(List list, T oldVal, T newVal) — заменяет все
заданные элементы новыми;
void reverse(List> list) — «переворачивает» список;
void rotate(List> list, int distance) — сдвигает список циклически на за-
данное число элементов;
void shuffle(List> list) — перетасовывает элементы списка;
singleton(T o), singletonList(T o), singletonMap(K key, V value) — создают
множество, список и карту отображения, позволяющие добавлять только один
элемент;
> void sort(List list),
void sort(List list, Comparator super T> c) — сортировка списка
естественным порядком и с использованием
Comparable или
Comparator
соответственно;
void swap(List> list, int i, int j) — меняет местами элементы списка, сто-
ящие
на заданных позициях;
КОЛЛЕКЦИИ
283
List unmodifiableList(List extends T> list) — возвращает ссыл-
ку на список с запрещением его модификации. Аналогичные методы есть и для
других видов коллекций.
/* # 23 # применение некоторых алгоритмов # AlgorithmDemo.java */
package
by.bsu.collect;
import
java.util.ArrayList;
import
java.util.Collections;
import
java.util.List;
public
class AlgoritmDemo {
public
static void main(String[ ] args) {
Comparator comp = new Comparator() {
public
int compare(Integer n, Integer m) {
eturn
m.intValue() - n.intValue();
}
};
ArrayList list = new ArrayList();
Collections.addAll(list, 1, 2, 3, 4, 5);
Collections.shuffle(list);
print(list);
Collections.sort(list, comp);
print(list);
Collections.reverse(list);
print(list);
Collections.rotate(list, 3);
print(list);
System.out.println("min: " + Collections.min(list, comp));
System.out.println("max: " + Collections.max(list, comp));
List singl = Collections.singletonList(71);
print(singl);
// singl.add(21); // ошибка времени выполнения
}
private
static void print(List c) {
for
(int i : c) {
System.out.print(i + " ");
}
System.out.println();
}
}
В результате будет выведено:
4 3 5 1 2
5 4 3 2 1
1 2 3 4 5
3 4 5 1 2
min: 5
max: 1
71
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
284
Задания к главе 10
Вариант A
1. Ввести строки из файла, записать в список. Вывести строки в файл в обрат-
ном порядке.
2. Ввести число, занести его цифры в стек. Вывести число, у которого цифры
идут в обратном порядке.
3. Создать в стеке индексный массив для быстрого доступа к записям в би-
нарном файле.
4. Создать список из элементов каталога и его подкаталогов.
5. Создать стек из номеров записи. Организовать прямой доступ к элементам
записи.
6. Занести стихотворения одного автора в список. Провести сортировку
по возрастанию длин строк.
7. Задать два стека, поменять информацию местами.
8. Определить множество на основе множества целых чисел. Создать методы
для определения пересечения и объединения множеств.
9. Списки (стеки, очереди) I(1..n) и U(1..n) содержат результаты n-измерений
тока и напряжения на неизвестном сопротивлении R. Найти приближен-
ное число R методом наименьших квадратов.
10. С использованием множества выполнить попарное суммирование произ-
вольного конечного ряда чисел по следующим правилам: на первом этапе
суммируются попарно рядом стоящие числа, на втором этапе суммируются
результаты первого этапа и т. д. до тех пор, пока не останется одно число.
11. Сложить два многочлена заданной степени, если коэффициенты многочле-
нов хранятся в объекте HashMap.
12. Умножить два многочлена заданной степени, если коэффициенты многоч-
ленов хранятся в различных списках.
13. Не используя вспомогательных объектов, переставить отрицательные эле-
менты данного списка в конец, а положитель ные — в начало списка.
14. Ввести строки из файла, записать в список ArrayList. Выполнить сорти-
ровку строк, используя метод sort() из класса Collections.
15. Задана строка, состоящая из символов «(», «)», «[», «]», «{», «}». Проверить
правильность расстановки скобок. Использовать стек.
16. Задан файл с текстом на английском языке. Выделить все различные слова.
Слова, отличающиеся только регистром букв, считать одинаковыми.
Использовать класс HashSet.
17. Задан файл с текстом на английском языке. Выделить все различные слова.
Для каждого слова подсчитать частоту его встречаемости. Слова, отличаю-
щиеся регистром букв, считать различными. Использовать класс HashMap.
КОЛЛЕКЦИИ
285
Вариант B
1. В кругу стоят N человек, пронумерованных от 1 до N. При ведении счета
по кругу вычеркивается каждый второй человек, пока не останется один.
Составить две программы, моделирующие процесс. Одна из программ
должна использовать класс ArrayList, а вторая — LinkedList. Какая из двух
программ работает быстрее? Почему?
2. Задан список целых чисел и число X. Не используя вспомогательных объ-
ектов и не изменяя размера списка, переставить элементы списка так, что-
бы сначала шли числа, не превосходящие X, а затем числа, больше X.
3. Написать программу, осуществляющую сжатие английского текста.
Построить для каждого слова в тексте оптимальный префиксный код по ал-
горитму Хаффмена. Использовать класс PriorityQueue.
4. Реализовать класс Graph, представляющий собой неориентированный
граф. В конструкторе класса передается количество вершин в графе.
Методы должны поддерживать быстрое добавление и удаление ребер.
5. На базе коллекций реализовать структуру хранения чисел с поддержкой
следующих операций:
• добавление/удаление числа;
• поиск числа, наиболее близкого к заданному (т. е. модуль разницы мини-
мален).
6. Реализовать класс, моделирующий работу N-местной автостоянки. Машина
подъезжает к определенному месту и едет вправо, пока не встретится сво-
бодное место. Класс должен поддерживать методы, обслуживающие при-
езд и отъезд машины.
7. Во входном файле хранятся две разреженные матрицы — А и В. По строить
циклически связанные списки СА и СВ, содержащие не нулевые элементы
соответственно матриц А и В. Просматривая списки, вычислить: а) сумму
S
= A + B; б) произведение P = A × B.
8. Во входном файле хранятся наименования некоторых объектов. Построить
список C1, элементы которого содержат наименова ния и шифры данных
объектов, причем элементы списка должны быть упорядочены по возраста-
нию шифров. Затем «сжать» список C1, удаляя дублирующие наименова-
ния объектов.
9. Во входном файле расположены два набора положительных чисел; между
наборами стоит отрицательное число. Построить два списка C1 и С2, эле-
менты которых содержат соответственно числа 1-го и 2-го набора таким
образом, чтобы внутри одного списка числа были упорядочены по возра-
станию. Затем объединить списки C1 и С2 в один упорядоченный список,
изменяя только значения полей ссылочного типа.
10. Во входном файле хранится информация о системе главных автодорог, связы-
вающих г. Минск с другими городами Беларуси. Используя эту информацию,
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
286
построить дерево, отображающее систему дорог респуб лики, а затем, продви-
гаясь по дереву, определить минимальный по длине путь из г. Минска в другой
заданный город. Предусмотреть возможность сохранения дерева в виртуаль-
ной памяти.
11. Один из способов шифрования данных, называемый «двойным шифрова-
нием», заключается в том, что исходные данные при помощи некоторого
преобразования последовательно шифруются на некоторые два ключа —
K1 и K2. Разработать и реализовать эффективный алгоритм, позволяю-
щий находить ключи K1 и K2 по исходной строке и ее зашифрованному
варианту. Проверить, оказался ли разработанный способ действительно
эффективным, протестировав программу для случая, когда оба ключа
являются 20-битными (время ее работы не должно превосходить одной
минуты).
12. На плоскости задано
N точек. Вывести в файл описания всех прямых, кото-
рые проходят более чем через одну точку из заданных. Для каждой прямой
указать, через сколько точек она проходит. Использовать класс
HashMap.
13. На клетчатой бумаге нарисован круг. Вывести в файл описания всех клеток,
целиком лежащих внутри круга, в порядке возрастания расстояния от клет-
ки до центра круга. Использовать класс
PriorityQueue.
14. На плоскости задано
N отрезков. Найти точку пересечения двух отрез ков,
имеющую минимальную абсциссу. Использовать класс
TreeMap.
15. На клетчатом листе бумаги закрашена часть клеток. Выделить все различ-
ные фигуры, которые образовались при этом. Фигурой считается набор за-
крашенных клеток, достижимых друг из друга при движении в четырех
направлениях. Две фигуры являются различными, если их нельзя совме-
стить поворотом на угол, кратный 90 градусам, и параллельным перено-
сом. Используйте класс
HashSet.
16. Дана матрица из целых чисел. Найти в ней прямоугольную подмат рицу, со-
стоящую из максимального количества одинаковых элементов. Использовать
класс
Stack.
17. Реализовать структуру «черный ящик», хранящую множество чисел и име-
ющую внутренний счетчик K, изначально равный нулю. Структура должна
поддерживать операции добавления числа в множество и возвращение
K-го
по минимальности числа из множества.
18. На прямой гоночной трассе стоит
N автомобилей, для каждого из которых
известны начальное положение и скорость. Определить, сколько произой-
дет обгонов.
19. На прямой гоночной трассе стоит
N автомобилей, для каждого из которых
известны начальное положение и скорость.
Вывести первые K обгонов.
КОЛЛЕКЦИИ
287
Тестовые задания к главе 10
Вопрос 10.1.
Какой класс коллекции позволяет наращивать и сокращать размер, предостав-
ляет индексный доступ к элементам, но его методы несинхронизированы (1)?
1) java.util.HashSet
2) java.util.ArrayList
3) java.util.LinkedHashSet
Вопрос 10.2.
Дан класс:
class X{
private int x;
public X(int x){this.x = x;}
public int hashCode(){
return 2;
}
}
Каков будет размер коллекции set после выполнения следующего кода (1)?
X obj1 = new X(1);
X obj2 = new X(1);
Set set = new HashSet();
set.add(obj1);
set.add(obj2);
1) 0;
2) 2;
3) 1;
4) при выполнении данного кода возникнет исключительная ситуация.
Вопрос 10.3.
Даны два фрагмента кода:
1.
Vector vct = new Vector();
vct.add("One"); vct.add("Two");
vct.add("Three"); vct.add("Four");
vct.add("Five"); vct.add("Six");
Iterator itr = vct.iterator();
vct.remove(0);
System.out.println(itr.next());
ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
288
2.
Vector vct = new Vector();
vct.add("One"); vct.add("Two");
vct.add("Three"); vct.add("Four");
vct.add("Five"); vct.add("Six");
Enumeration enm = vct.elements();
vct.remove(0);
System.out.println(enm.nextElement());
Укажите разницу при выполнении первого и второго фрагмента кода (1):
1) при выполнении первого фрагмента на консоль выведется строка «Two»,
а при выполнении второго произойдет исключительная ситуация;
2) при выполнении второго фрагмента на консоль выведется строка «Two»,
а при выполнении первого произойдет исключительная ситуация;
3) выполнение двух фрагментов кода приведет к исключительной ситуации;
4) при выполнении двух фрагментов кода на консоль выведется строка «Two»;
5) при компиляции первого фрагмента приведет к ошибке;
6) компиляция второго фрагмента кода приведет к ошибке;
7) компиляция двух фрагментов кода приведет к ошибке.
Вопрос 10.4.
Дан код:
import java.util.*;
enum PCounter {UNO, DOS, TRES, CUATRO, CINCO, SEIS, SIETE};
public class Quest {
public static void main(String[] args) {
EnumSet
enst1 = EnumSet.range(PCounter.TRES, PCounter.CINCO);//1
EnumSet
enst2 = EnumSet.complementOf(enst1);//2
System.out.println(enst2);
}}
Что будет выведено при попытке компиляции и запуска программы (1)?
1) [UNO, DOS, TRES, CINCO, SEIS, SIETE]
2) [DOS, TRES, CUATRO, CINCO, SEIS]
3) [UNO, DOS, SEIS, SIETE]
4) ошибка компиляции в строке 1
5) ошибка компиляции в строке 2
6) ничего из перечисленного
КОЛЛЕКЦИИ
Вопрос 10.5.
Что будет результатом компиляции и запуска следующей программы (1)?
import java.util.*;
public class Quest {
public static void main(String[] args) {
NavigableMap nmap = new TreeMap();
nmap.put("one", new Integer(1)); nmap.put("two", new Integer(2));
nmap.put("three", new Integer(3)); nmap.put("four", new Integer(4));
Map map = nmap.headMap("three");
System.out.println(map);
}}
1) {four=4, one=1}
2) {one=2, two=2}
3) {three=3, four=4}
4) {four=4, one=1, three=3}
5) {one=2, two=2, three=3}
290
Достарыңызбен бөлісу: