Утилитарный класс Java Collections
Используйте класс Collections в Java для сортировки, поиска, перестановки и оборачивания коллекций.
java.util.Collections — это стандартная библиотека статических вспомогательных методов для работы с коллекциями. Думайте о нём так же, как вы думаете о java.util.Arrays: final-класс без состояния экземпляра, только статические методы. Вы никогда не пишете new Collections() — вы пишете Collections.sort(list), Collections.shuffle(list), Collections.unmodifiableMap(map).
Легко перепутать этот класс с интерфейсом, рядом с которым он находится: Collection<E> (интерфейс, с заглавной C и без s) — это супертип List, Set и Queue; Collections (класс, множественное число) — это утилитарный инструментарий. Этот класс не реализует интерфейс — он просто работает с коллекциями, которые его реализуют.
Обзор инструментария
Методы разбиваются на шесть тематических групп. Мы рассмотрим каждую из них, а в следующих двух главах подробнее остановимся на сортировке и поиске.
1. Упорядочивание и перестановка
Collections.sort(list); // natural order — requires Comparable
Collections.sort(list, comparator); // custom comparator
Collections.reverse(list); // in place
Collections.shuffle(list); // pseudo-random permutation
Collections.shuffle(list, new Random(42)); // deterministic shuffle with a seeded RNG
Collections.rotate(list, 2); // [a,b,c,d,e] → [d,e,a,b,c]
Collections.swap(list, 0, list.size() - 1); // swap two indicessort — это стабильная сортировка слиянием: равные элементы сохраняют относительный порядок. shuffle выполняет перемешивание Фишера–Йетса, которое является равномерно случайным при использовании соответствующего генератора случайных чисел. rotate — это именно то, что нужно, когда вы хотите «сдвинуть всё на N позиций с переносом через края». reverse, swap и rotate изменяют список на месте и не возвращают ничего полезного.
2. Поиск
int i = Collections.binarySearch(sortedList, key); // O(log n) — list must be sorted
int j = Collections.binarySearch(sortedList, key, comparator);
T max = Collections.max(coll);
T min = Collections.min(coll, comparator);
int n = Collections.frequency(coll, target); // how many times target appears
boolean disjoint = Collections.disjoint(a, b); // no element in common?У binarySearch есть своя глава — вкратце: список уже должен быть отсортирован в том же порядке, который использует поиск, а отрицательный результат означает «не найдено, но точку вставки можно вычислить как -result - 1».
3. Заполнение, копирование, замена
Collections.fill(list, "x"); // overwrite every slot with "x"
Collections.copy(dest, src); // copy src into dest; dest.size() must be ≥ src.size()
Collections.replaceAll(list, "old", "new"); // returns true if anything changed
Collections.nCopies(5, "x"); // immutable list with "x" 5 times
Collections.singleton(value); // immutable Set of one
Collections.singletonList(value); // immutable List of one
Collections.singletonMap(k, v); // immutable Map of one entry
Collections.emptyList(); Collections.emptyMap(); Collections.emptySet();Фабрики empty/singleton/nCopies возвращают кешированные, неизменяемые экземпляры — они не выделяют память при каждом вызове. Это небольшая бесплатная оптимизация, когда вам нужна заведомо пустая или очень маленькая коллекция.
4. Синхронизированные обёртки (в основном исторические)
List<String> lockedList = Collections.synchronizedList(new ArrayList<>());
Map<String, Int> lockedMap = Collections.synchronizedMap(new HashMap<>());
Set<String> lockedSet = Collections.synchronizedSet(new HashSet<>());Эти обёртки заставляют каждый метод захватывать блокировку на объекте-обёртке. Применяется та же оговорка, что и для Hashtable: составные операции по-прежнему подвержены гонкам, а итераторы обязательно должны быть обёрнуты в явные блоки synchronized (wrapper) { ... }:
synchronized (lockedList) {
for (String s : lockedList) { ... } // safe: holds the lock for the whole walk
}В современном коде лучше использовать ConcurrentHashMap, CopyOnWriteArrayList и ConcurrentSkipListSet. Синхронизированные обёртки существуют для адаптации потоконебезопасного API к потокобезопасному, когда другого варианта нет.
5. Неизменяемые обёртки
List<String> frozen = Collections.unmodifiableList(mutableList);
Set<String> frozenS = Collections.unmodifiableSet(mutableSet);
Map<K, V> frozenM = Collections.unmodifiableMap(mutableMap);Эти обёртки заставляют методы-мутаторы выбрасывать UnsupportedOperationException. Исходная коллекция по-прежнему изменяема — обёртка является представлением только для чтения. Изменения через оригинал отображаются через представление. Это ключевое отличие от фабрик List.of(...) / Set.of(...) / Map.of(...), которые создают полностью неизменяемые коллекции с собственным хранилищем. В следующей главе оба подхода сравниваются.
6. Однофункциональные и типобезопасные представления
List<Object> objects = new ArrayList<>();
List<String> safe = Collections.checkedList(objects, String.class);
safe.add("ok"); // fine
((List) safe).add(42); // throws ClassCastException immediately, not latercheckedList, checkedSet, checkedMap устанавливают проверку типа во время выполнения при каждой вставке. Полезно в унаследованном коде, передающем обобщённые коллекции через API с типом Object — обёртка громко сигнализирует об ошибке в точке вставки, а не значительно позже в точке извлечения.
Несколько небольших, но ценных методов
Collections.disjoint(a, b)возвращаетtrue, если ни один элементaне входит вb. Идиоматично для вопроса «есть ли какое-либо пересечение между этими двумя множествами?»Collections.frequency(coll, target)подсчитывает вхождения — намного понятнее, чемcoll.stream().filter(x -> x.equals(target)).count().Collections.nCopies(n, x)иногда именно то, что нужно, напримерresult.addAll(Collections.nCopies(rows, "pad")). Возвращаемый список неизменяем, но потребляет O(1) памяти независимо отn— это виртуальный список, а не массив.Collections.reverse(list)работает на месте и стабильно. Не реализуйте это самостоятельно черезfor-цикл.Collections.addAll(coll, "a", "b", "c")короче и быстрее, чемcoll.addAll(List.of("a", "b", "c")), так как не создаёт промежуточного списка.
Чем Collections не является
- Не замена Stream. Для фильтрации/отображения/свёртки используйте потоки.
Collectionsработает с мутацией и прямыми запросами, а не с декларативными цепочками. - Не место для
List.of/Set.of/Map.of. Это фабрики на интерфейсах, добавленные в Java 9. Они соседствуют сCollections.unmodifiableList, но не являются частью этого класса. - Не место для сборщиков потоков. Это
java.util.stream.Collectors. Другой пакет, другая роль.
Практический пример: инструментарий в одной программе
Программа ниже применяет дюжину методов Collections к одному списку и одному словарю, чтобы сделать API наглядным: sort, reverse, shuffle, rotate, swap, binarySearch, min/max, frequency, disjoint, fill, replaceAll и неизменяемое представление.
Что следует извлечь из результата:
- Каждый метод либо изменяет список на месте (
sort,reverse,shuffle,rotate,swap,fill,replaceAll), либо возвращает примитивный ответ (min,max,frequency,disjoint,binarySearch). Ни один метод в инструментарии не возвращает «новый» отсортированный список —Collections.sortизменяет тот, который вы ему передали. binarySearchвернул индекс"delta"и отрицательное значение для"zeta". Соглашение-result - 1даёт точку вставки, которая сохранила бы список отсортированным.replaceAllпереписал одну строку везде, где она встречалась;fillперезаписал каждый элемент. Оба метода работают с одним и тем же списком — удобно, когда нужно повторно использовать хранилище.Collections.unmodifiableList(backing)вернул представление только для чтения. Это представление выбросило исключение приadd, но изменение исходного списка по-прежнему сработало, и изменение отразилось через представление. Представление — это не копия.
Что дальше
Теперь инструментарий у вас в голове на уровне оглавления. Две операции заслуживают более пристального внимания, поскольку детали имеют значение: Сортировка коллекций Java (когда использовать Collections.sort против List.sort versus stream().sorted(), стабильный порядок, построители компараторов, примитивные специализации) и Поиск в коллекциях Java (contains, indexOf, binarySearch и поиск на основе потоков). Следующая глава посвящена сортировке.