Интерфейс Collection в Java
Корневой интерфейс Collection в Java и контракт, который наследуют список, множество и очередь.
java.util.Collection<E> — это корень той части фреймворка, которая хранит отдельные элементы: каждый List, Set, Queue и Deque его реализует (единственное семейство, которое не делает этого, — Map, чьи элементы представляют собой записи, а не одиночные значения). Всё, что можно сделать «вне зависимости от того, какая это группа» — добавить, удалить, проверить contains, выполнить итерацию, посчитать элементы, преобразовать в массив, открыть поток — объявлено здесь. Эта глава описывает контракт: методы, на которые можно рассчитывать в любой коллекции, те немногие, что могут бросать исключения, и модель итерации, которую наследует каждая реализация.
Иерархия с первого взгляда
Iterable<E>
└── Collection<E>
├── List<E> — ordered, indexed, duplicates allowed
├── Set<E> — no duplicates
│ └── SortedSet<E> → NavigableSet<E>
└── Queue<E> — "next in line"
└── Deque<E> — double-ended queueCollection расширяет Iterable<E>, именно поэтому каждая коллекция работает с циклом for-each. Две специализации Collection — Set и Queue — уточняют контракт; Map является самостоятельным корнем.
Основные методы, присутствующие в каждой коллекции
Ниже приведён полный набор методов экземпляра интерфейса Collection, сгруппированных по назначению. Лучше запомнить категории, а не весь список — как только вы узнаете, что существует метод removeIf, вы сможете его найти.
Размер и пустота
int size()— количество элементов.boolean isEmpty()—size() == 0, но зачастую работает быстрее.
Добавление
boolean add(E e)— добавляет один элемент. Возвращаетtrue, если коллекция изменилась. (ДляSetвозвращаетfalseпри попытке добавить дубликат.)boolean addAll(Collection<? extends E> c)— добавляет каждый элемент изc.
Удаление
boolean remove(Object o)— удаляет одно вхождениеo.boolean removeAll(Collection<?> c)— удаляет каждый элемент, присутствующий вc.boolean retainAll(Collection<?> c)— оставляет только элементы изc(пересечение).boolean removeIf(Predicate<? super E> filter)— удаляет каждый элемент, соответствующий предикату. Наиболее чистый способ фильтрации на месте.void clear()— очищает коллекцию.
Запросы
boolean contains(Object o)— проверка вхождения.boolean containsAll(Collection<?> c)— проверка подмножества.
Итерация и групповые представления
Iterator<E> iterator()— базовый итератор; используется цикломfor-each.Stream<E> stream()/parallelStream()— открываетStreamпо элементам.void forEach(Consumer<? super E> action)— унаследован отIterable. Функциональная форма итерации.
Преобразование в массив
Object[] toArray()<T> T[] toArray(T[] a)и более новый<T> T[] toArray(IntFunction<T[]> generator)(Java 11+) — типизированный массив.
Это весь интерфейс. Каждый список, множество и очередь, с которыми вы будете работать в этой части, — лишь разные реализации этих методов плюс несколько собственных.
Равенство и equals / hashCode
Collection.equals(Object) не определён на корневом уровне — каждый подинтерфейс сам задаёт, что для него означает равенство. List требует одинакового порядка и одинаковых элементов; Set требует одинаковых элементов вне зависимости от порядка; Queue вообще не определяет equals (очередь на основе LinkedList и очередь на основе ArrayDeque с одинаковым содержимым не будут равны, так как сравнение возвращается к сравнению по идентичности). Не сравнивайте коллекции из разных семейств, ожидая симметрии.
Элементы, хранящиеся в Collection, должны иметь правильно реализованные equals / hashCode, если вы хотите, чтобы методы contains, remove и (для коллекций на основе хэш-таблиц) поиск работали корректно. Контракт этих методов описан в главе equals и hashCode — это необходимое условие для использования Set и Map с вашими классами.
Необязательные операции
Некоторые коллекции являются немодифицируемыми — List.of(1,2,3), Collections.unmodifiableList(list), представления, возвращаемые Map.keySet() в определённых реализациях, и т. д. Они по-прежнему реализуют Collection, но вызов методов add, remove, clear или любого мутирующего метода бросает UnsupportedOperationException. В Javadoc они называются «необязательными операциями». Это наиболее близкое в Java к отказу от части интерфейса во время выполнения; цена такого подхода — компилятор не может обнаружить ошибку, и вы узнаёте о ней только при первом исключении.
Безопасное правило: если вы не создавали коллекцию сами, считайте её потенциально немодифицируемой. Если вам нужна изменяемая копия, сначала выполните new ArrayList<>(received).
Итерация: три формы, один базовый механизм
Каждая коллекция поддерживает три стиля итерации, и все три в итоге вызывают iterator():
Collection<String> names = List.of("Ada", "Linus", "Grace");
// 1. for-each — the everyday form
for (String n : names) System.out.println(n);
// 2. forEach with a lambda — declarative
names.forEach(System.out::println);
// 3. Iterator — when you need to remove during iteration
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.startsWith("L")) it.remove(); // safe; for-each can't do this
}Почему формы 1 и 3 по-прежнему обе актуальны: цикл for-each не может изменять базовую коллекцию, не бросив ConcurrentModificationException. Когда нужно удалять элементы во время итерации, вы прибегаете к явному Iterator. Глава Итераторы далее в этой части подробно описывает протокол работы с ними.
Алгебра множеств с помощью групповых операций
Групповые операции превращают коллекцию в вычислитель алгебры множеств (независимо от того, является ли она Set — они работают и с List):
Collection<Integer> a = new ArrayList<>(List.of(1, 2, 3, 4));
Collection<Integer> b = List.of(3, 4, 5);
a.addAll(b); // union (multiset)
a.retainAll(List.of(3, 4)); // intersection
a.removeAll(List.of(3)); // differenceЭто безопасный, независимый от сторонних библиотек способ выразить «оставить только элементы, присутствующие также в b», без написания цикла. Операции изменяют получателя — если вам нужен неизменяемый результат, сначала скопируйте.
Практический пример: все методы рядом
Программа ниже применяет каждую категорию методов Collection к одному ArrayList, чтобы вы могли видеть их в одном месте и наблюдать, как работает контракт.
Стоит обратить внимание на два момента в выводе программы:
remove("red")удалил только первое вхождение — таков контрактCollection. Чтобы удалить все совпадения, используйтеremoveIf(далее в примере он удаляет каждое слово длиннее четырёх символов).UnsupportedOperationExceptionотfrozen.add("d")— это правило «необязательных операций» в действии.frozenреализуетCollection, поэтому вызов компилируется. Реализация решила не поддерживать данную операцию, и вы узнаёте об этом во время выполнения.
Что дальше
Collection — абстрактный контракт. Первое конкретное уточнение, с которым вы познакомитесь, — то, которое добавляет порядок и индексацию — интерфейс List. Именно там появляются доступ по индексу, подсписки и операции, сохраняющие порядок.