Коллекции ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-10-21

Коллекции в Java: Collection Framework и иерархия коллекций

Попробуйте написать Collection<String> names = new HashMap<String, String>(); — компилятор ответит incompatible types: HashMap<String,String> cannot be converted to Collection<String>. HashMap входит в Collection Framework, но интерфейс Collection не реализует. Чтобы такие сюрпризы не случались, сначала нужно увидеть иерархию коллекций целиком.

Collection Framework (Java Collections Framework) — это набор интерфейсов, реализаций и алгоритмов из пакета java.util для хранения и обработки групп объектов: списков, множеств, очередей и отображений «ключ–значение». В отличие от массива, размер коллекции меняется динамически, а готовые методы позволяют искать, сортировать и фильтровать элементы без ручного кода. Коллекции появились в J2SE 1.2 (1998), обобщённые типы в них — в Java 5.

Из чего состоит Collection Framework

Collection Framework в Java состоит из трёх частей: интерфейсов, которые описывают поведение коллекции (список, множество, очередь, отображение), реализаций — готовых классов вроде ArrayList и HashMap, и алгоритмов — статических методов для сортировки, поиска и перемешивания.

Часть Что это Примеры Где разбирается
Интерфейсы Контракт: какие операции поддерживает коллекция Collection, List, Set, Queue, Deque, Map Интерфейс Collection
Реализации Классы с конкретной структурой данных внутри ArrayList, LinkedList, HashSet, TreeSet, ArrayDeque, HashMap Уроки по каждому классу — см. карту модуля
Алгоритмы Статические методы классов Collections и Arrays Collections.sort(), binarySearch(), shuffle(), max() Методы класса Collections

Переменную принято объявлять типом интерфейса, а реализацию указывать только в new: List<String> names = new ArrayList<>();. Тогда реализацию можно заменить одной строкой, не трогая остальной код. Сами структуры данных (список, стек, очередь, множество) без привязки к Java описаны в уроке Структуры данных в Java.

Иерархия коллекций Java

Иерархия коллекций Java состоит из двух независимых веток. Первая начинается с интерфейса Collection (он расширяет Iterable) и делится на List, Set и Queue. Вторая — интерфейс Map для пар «ключ–значение»: он входит в Collection Framework, но Collection не наследует.

Иерархия коллекций Java: интерфейсы Collection, List, Set, Queue, Map и их реализации

Та же иерархия в текстовом виде (стрелка → означает «расширяет / реализует», после тире указаны основные классы):

Iterable<T>
└── Collection<E>
    ├── List<E>            — ArrayList, LinkedList, Vector → Stack
    ├── Set<E>             — HashSet → LinkedHashSet
    │   └── SortedSet<E> → NavigableSet<E>   — TreeSet
    └── Queue<E>           — PriorityQueue
        └── Deque<E>       — ArrayDeque, LinkedList

Map<K,V>                   — HashMap → LinkedHashMap, Hashtable
└── SortedMap<K,V> → NavigableMap<K,V>  — TreeMap

Обратите внимание: LinkedList встречается в двух ветках — он реализует и List, и Deque. Vector, Stack и Hashtable — классы из Java 1.0, встроенные в иерархию задним числом; в новом коде их не используют (урок Legacy Classes).

Что изменилось в Java 21

В Java 21 (JEP 431) в иерархию добавлены интерфейсы SequencedCollection, SequencedSet и SequencedMap для коллекций с определённым порядком. Их расширяют List, Deque, SortedSet, SortedMap, а реализуют также LinkedHashSet и LinkedHashMap. Отсюда методы getFirst(), getLast(), removeFirst(), removeLast() и reversed() у обычного ArrayList. На Java 17 вызов list.getFirst() не скомпилируется — там пишут list.get(0).

Основные интерфейсы коллекций

Основных интерфейсов коллекций шесть: List (упорядоченный список с индексами), Set (множество без дубликатов), SortedSet/NavigableSet (отсортированное множество), Queue (очередь), Deque (двусторонняя очередь и стек) и Map (пары «ключ–значение»). Различаются они тем, допускают ли дубликаты и в каком порядке отдают элементы.

Интерфейс Что моделирует Дубликаты Порядок элементов Урок
Collection Корень ветки: add(), remove(), contains(), size(), iterator() Зависит от наследника Зависит от наследника Collection
List Последовательность с доступом по индексу Да Порядок вставки List и ArrayList
Set Множество уникальных элементов Нет (add() вернёт false) Не гарантирован (HashSet) или порядок вставки (LinkedHashSet) Set, HashSet
SortedSet / NavigableSet Отсортированное множество с поиском ближайших элементов Нет По возрастанию или по Comparator SortedSet и TreeSet
Queue Очередь: добавление в хвост, извлечение из головы Да FIFO или по приоритету (PriorityQueue) Queue и Deque
Deque Двусторонняя очередь; заменяет класс Stack Да FIFO или LIFO — с обоих концов ArrayDeque
Map Отображение уникальных ключей на значения Ключи — нет, значения — да Зависит от реализации Map

Как сравниваются элементы в отсортированных коллекциях, объясняет урок Comparable и Comparator, а как их перебирать — урок Iterator.

Какую реализацию выбрать

Реализацию выбирают по трём вопросам: нужен ли доступ по индексу, важен ли порядок элементов и нужна ли сортировка. В большинстве задач хватает ArrayList, HashSet, HashMap и ArrayDeque. Остальные классы нужны, когда требуется особый порядок или быстрые вставки на концах списка.

Класс Внутри Порядок null Основные операции
ArrayList Динамический массив Вставки Да get(i) O(1), добавление в конец O(1) в среднем, вставка в середину O(n)
LinkedList Двусвязный список Вставки Да get(i) O(n), добавление/удаление на концах O(1)
ArrayDeque Кольцевой массив Вставки (с обоих концов) Нет — NullPointerException Операции на концах O(1)
PriorityQueue Двоичная куча По приоритету при извлечении Нет — NullPointerException offer()/poll() O(log n), peek() O(1)
HashSet Хеш-таблица (внутри HashMap) Не гарантирован Один null add()/contains() O(1) в среднем
LinkedHashSet Хеш-таблица + связный список Вставки Один null O(1) в среднем
TreeSet Красно-чёрное дерево Сортировка Нет — NullPointerException при естественном порядке O(log n)
HashMap Хеш-таблица Не гарантирован Один null-ключ, любые null-значения get()/put() O(1) в среднем
LinkedHashMap Хеш-таблица + связный список Вставки Как у HashMap O(1) в среднем
TreeMap Красно-чёрное дерево Сортировка ключей null-ключ — NullPointerException при естественном порядке O(log n)

Упорядоченные и отсортированные коллекции

Столбец «Порядок» в таблице описывает два разных свойства. Упорядоченная (ordered) коллекция перебирает элементы в предсказуемом порядке, который не зависит от их значений: по индексу или по порядку вставки. Отсортированная (sorted) коллекция сама расставляет элементы по их значениям — в естественном порядке (Comparable) или по правилу Comparator.

Сочетание Возможно? Примеры
Неупорядоченная и неотсортированная Да HashSet, HashMap
Упорядоченная, но неотсортированная Да ArrayList, LinkedList, LinkedHashSet, LinkedHashMap
Упорядоченная и отсортированная Да TreeSet, TreeMap
Отсортированная, но неупорядоченная Нет — сортировка сама задаёт порядок, поэтому любая отсортированная коллекция упорядочена
Set<String> linked = new LinkedHashSet<>(List.of("pear", "apple", "fig"));
Set<String> sorted = new TreeSet<>(List.of("pear", "apple", "fig"));
System.out.println(linked); // [pear, apple, fig] — порядок вставки
System.out.println(sorted); // [apple, fig, pear] — по алфавиту

Если важно, в каком порядке элементы вернутся при обходе, «упорядоченной» коллекции может оказаться недостаточно: LinkedHashSet сохранит порядок добавления, но не отсортирует данные. PriorityQueue — особый случай: poll() всегда отдаёт наименьший элемент, но итератор обходит кучу без сортировки.

Почему LinkedList проигрывает ArrayList при доступе по индексу, разобрано в уроке LinkedList, а зачем элементам HashSet и ключам HashMap методы equals() и hashCode() — в уроке Set и HashSet.

Обобщённые коллекции

Все интерфейсы и классы Collection Framework обобщённые (с Java 5): тип элементов указывают в угловых скобках, например List<String>. Компилятор не даст положить в такую коллекцию объект другого типа, а при чтении не потребуется приведение типа. Коллекция без параметра (raw type) компилируется только с предупреждением и ломается уже во время выполнения.

import java.util.ArrayList;
import java.util.List;

public class RawListExample {
    public static void main(String[] args) {
        List raw = new ArrayList();      // raw type: компилятор выдаст только warning
        raw.add("Java");
        raw.add(42);                     // ошибки нет — тип элементов не проверяется

        for (Object item : raw) {
            String s = (String) item;    // на втором элементе — исключение
            System.out.println(s.toUpperCase());
        }
    }
}

Вывод:

JAVA
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
	at RawListExample.main(RawListExample.java:11)

С объявлением List<String> list = new ArrayList<>(); строка list.add(42) не скомпилируется — ошибка найдена до запуска. Примитивы (int, double) в коллекции не кладут: используется класс-обёртка (List<Integer>) и автоупаковка. Wildcard <? extends T> и <? super T> в параметрах методов объясняет урок принцип PECS.

Неизменяемые коллекции

Неизменяемая (unmodifiable) коллекция — это коллекция, у которой методы add(), remove() и set() бросают UnsupportedOperationException. В современном Java их создают фабричными методами List.of(), Set.of(), Map.of() (Java 9) и List.copyOf() (Java 10). Различаются способы деталями: поддержкой null и связью с исходными данными.

Способ С версии Что можно менять null-элементы Связь с источником
Arrays.asList(...) 1.2 Только set(); add()/remove() — UnsupportedOperationException Да Представление массива: изменения видны в обе стороны
Collections.unmodifiableList(list) 1.2 Ничего Да Представление: изменения исходного списка видны
List.of(...), Set.of(...), Map.of(...) 9 Ничего Нет — NullPointerException (даже у contains(null)) Нет
List.copyOf(collection) 10 Ничего Нет — NullPointerException Нет, это копия
stream.toList() 16 Ничего Да Нет

Что выведет этот код?

import java.util.Arrays;
import java.util.List;

public class UnmodifiableExample {
    public static void main(String[] args) {
        List<String> fixed = Arrays.asList("a", "b");
        fixed.set(0, "z");
        System.out.println(fixed);

        try {
            fixed.add("c");
        } catch (UnsupportedOperationException e) {
            System.out.println("Arrays.asList: add() запрещён");
        }

        List<String> immutable = List.of("a", "b");
        try {
            immutable.set(0, "z");
        } catch (UnsupportedOperationException e) {
            System.out.println("List.of: set() запрещён");
        }
    }
}
[z, b]
Arrays.asList: add() запрещён
List.of: set() запрещён

Arrays.asList() возвращает список фиксированного размера, а не неизменяемый: заменить элемент можно, изменить размер — нет. Почему изменения такого списка попадают в исходный массив, показано в уроке Backed Collections. Если нужен обычный изменяемый список с начальными элементами, оберните фабрику: new ArrayList<>(List.of("a", "b")).

Потокобезопасные коллекции Java

Обычные ArrayList, HashSet и HashMap не потокобезопасны: при одновременной записи из нескольких потоков данные теряются или итерация падает с ConcurrentModificationException. Для многопоточного кода в Java есть три группы коллекций: унаследованные синхронизированные классы, обёртки Collections.synchronizedXxx() и классы пакета java.util.concurrent (Java 5).

Коллекция Как обеспечивается потокобезопасность Когда использовать
Vector, Hashtable Каждый метод synchronized Только в старом коде; замена — классы ниже
Collections.synchronizedList(), synchronizedMap() Обёртка с одной блокировкой на все методы; перебор нужно вручную заключать в synchronized (list) { ... } Редкий доступ из нескольких потоков к готовой коллекции
ConcurrentHashMap Блокировка отдельных корзин и CAS-операции; чтение без блокировок. null-ключи и значения запрещены Основной выбор для общей Map (кеши, счётчики)
CopyOnWriteArrayList, CopyOnWriteArraySet Каждая запись копирует внутренний массив; итератор работает со снимком и не бросает ConcurrentModificationException Много чтений, редкие изменения (списки слушателей)
ConcurrentLinkedQueue, ConcurrentLinkedDeque Неблокирующие алгоритмы на CAS Общая очередь без ожидания
ArrayBlockingQueue, LinkedBlockingQueue (интерфейс BlockingQueue) put() ждёт свободного места, take() — появления элемента Схема «производитель–потребитель»
ConcurrentSkipListMap, ConcurrentSkipListSet Список с пропусками (skip list) без глобальной блокировки Потокобезопасная замена TreeMap/TreeSet

Пример: два потока по 10 000 раз увеличивают общий счётчик в ConcurrentHashMap. Метод merge() выполняет чтение и запись атомарно, поэтому ни одно увеличение не теряется:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentCounter {
    public static void main(String[] args) throws InterruptedException {
        Map<String, Integer> counts = new ConcurrentHashMap<>();
        Runnable task = () -> {
            for (int i = 0; i < 10_000; i++) {
                counts.merge("clicks", 1, Integer::sum);
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(counts.get("clicks")); // Вывод: 20000
    }
}

Если заменить ConcurrentHashMap на HashMap, результат станет непредсказуемым: обычно меньше 20000, потому что потоки перезаписывают изменения друг друга. Синхронизация потоков подробно разбирается в уроке Синхронизация потоков.

Уроки модуля по порядку

Модуль «Коллекции» идёт от общего к частному: сначала корневой интерфейс и структуры данных, затем каждая ветка иерархии — списки, множества, очереди, отображения, и в конце инструменты для работы с ними.

Урок Что в нём разбирается
Интерфейс Collection Методы, общие для всех коллекций
Структуры данных Список, стек, очередь, множество — без привязки к классам
List и ArrayList Методы List, устройство ArrayList, toArray()
Set, HashSet, LinkedHashSet Хеш-таблица, контракт equals() и hashCode()
SortedSet и TreeSet Отсортированные множества, subSet(), headSet()
Comparable и Comparator Как задать порядок сортировки объектов
NavigableSet ceiling(), floor(), higher(), lower()
Queue и Deque Методы очередей: offer(), poll(), peek(), push(), pop()
ArrayDeque Очередь и стек на массиве
LinkedList Двусвязный список, сравнение с ArrayList
PriorityQueue Очередь с приоритетами
Принцип PECS Wildcard extends и super в коллекциях
Iterator Перебор коллекций, ConcurrentModificationException
Map HashMap, TreeMap, LinkedHashMap, SortedMap, NavigableMap
Методы класса Collections sort(), binarySearch(), shuffle(), frequency()
Backed Collections Представления коллекций и Arrays.asList()
Legacy Classes Vector, Stack, Hashtable, Properties, Enumeration

Презентация с видео на Patreon →

Часто задаваемые вопросы

Чем Collection отличается от Collections?

Collection — интерфейс, корень ветки списков, множеств и очередей. Collections — служебный класс со статическими методами: sort(), reverse(), unmodifiableList(), synchronizedMap() и другими. Создать объект Collections нельзя, а Collection реализуют все списки, множества и очереди.

Почему Map не наследует интерфейс Collection?

Элемент Map — пара «ключ–значение», а методы Collection рассчитаны на одиночные элементы: add(E) для пары не имеет смысла. Поэтому Map — отдельная ветка иерархии, а с остальными коллекциями её связывают представления keySet(), values() и entrySet().

Можно ли хранить int в коллекции?

Напрямую нельзя: List<int> не скомпилируется, используется List<Integer> с автоупаковкой. Тут легко ошибиться: для List<Integer> вызов list.remove(1) удалит элемент с индексом 1, а не число 1. Чтобы удалить значение, пишут list.remove(Integer.valueOf(1)).

Какие коллекции использовать по умолчанию?

Для списка — ArrayList, для множества — HashSet, для отображения — HashMap, для очереди и стека — ArrayDeque вместо устаревшего Stack. Остальные реализации берут, когда нужен порядок вставки, сортировка или потокобезопасность. Сравнение классов — в разделе Какую реализацию выбрать.

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

unknown Jul 9, 2024
спасибо за отличный курс!

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.