W3docs

Утилитарный класс 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 indices

sort — это стабильная сортировка слиянием: равные элементы сохраняют относительный порядок. 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 later

checkedList, 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 и неизменяемое представление.

java— editable, runs on the server

Что следует извлечь из результата:

  • Каждый метод либо изменяет список на месте (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 и поиск на основе потоков). Следующая глава посвящена сортировке.

Практика

Практика
Вы сортируете `List<String>` с помощью `Collections.sort(list)`, затем вызываете `Collections.binarySearch(list, 'zeta')` и получаете результат `-4`. Что означает `-4`?
Вы сортируете `List<String>` с помощью `Collections.sort(list)`, затем вызываете `Collections.binarySearch(list, 'zeta')` и получаете результат `-4`. Что означает `-4`?
Was this page helpful?