Класс Collections в Java: методы и примеры
Вызовите Collections.binarySearch(list, "зеленый") на списке [красный, синий, зеленый] — метод вернёт -1, то есть «не найдено», хотя элемент лежит в позиции 2. Исключения не будет: binarySearch() рассчитан только на отсортированный список. Такие неявные условия есть почти у каждого метода класса Collections, и ниже они собраны вместе с выводом программ.
Класс java.util.Collections — служебный (utility) класс Collection Framework, который состоит только из статических методов для работы с коллекциями: сортировки, бинарного поиска, перемешивания, переворота, поиска максимума и минимума, подсчёта элементов, а также создания неизменяемых, синхронизированных и проверяемых обёрток. Конструктор класса закрытый, создать объект Collections нельзя. Большинство методов принимает List, часть — любую Collection. Не путайте его с интерфейсом Collection — разница объяснена в уроке Коллекции в Java.
Основные методы класса Collections
Основные методы класса Collections делятся на три группы: алгоритмы над списком (sort(), binarySearch(), reverse(), shuffle(), swap(), fill(), copy(), rotate()), вычисления над любой коллекцией (max(), min(), frequency(), disjoint()) и фабрики списков и обёрток (nCopies(), emptyList(), unmodifiableList(), synchronizedList()). Все методы статические и вызываются через имя класса.
| Метод | Что делает | Граничный случай / ошибка |
|---|---|---|
sort(list), sort(list, cmp) | Сортирует список на месте, стабильно, за O(n log n) | Список из List.of() — UnsupportedOperationException; null-элемент при естественном порядке — NullPointerException |
binarySearch(list, key), binarySearch(list, key, cmp) | Возвращает индекс элемента или -(точка вставки) - 1 | Список не отсортирован (или отсортирован другим компаратором) — результат не определён |
reverse(list) | Переворачивает порядок элементов | Пустой список или из одного элемента — ничего не меняется |
shuffle(list), shuffle(list, rnd) | Случайно переставляет элементы | С new Random(seed) порядок одинаков при каждом запуске |
swap(list, i, j) | Меняет местами два элемента | Индекс вне [0, size()) — IndexOutOfBoundsException |
fill(list, obj) | Заменяет все элементы одним значением | Размер не меняет: пустой список останется пустым |
nCopies(n, obj) | Неизменяемый список из n одинаковых элементов | n < 0 — IllegalArgumentException; add() — UnsupportedOperationException |
max(coll), min(coll), версии с cmp | Наибольший / наименьший элемент | Пустая коллекция — NoSuchElementException |
copy(dest, src) | Копирует элементы src поверх начала dest | dest.size() < src.size() — IndexOutOfBoundsException: Source does not fit in dest |
rotate(list, distance) | Циклически сдвигает элементы на distance позиций | Отрицательное значение сдвигает влево; distance берётся по модулю размера |
frequency(coll, obj) | Считает, сколько раз элемент встречается в коллекции | Сравнение через equals(); obj = null допустим |
disjoint(c1, c2) | true, если у коллекций нет общих элементов | Две пустые коллекции — true |
addAll(coll, e1, e2, ...) | Добавляет перечисленные элементы, возвращает true, если коллекция изменилась | Список из Arrays.asList() — UnsupportedOperationException |
reverseOrder(), reverseOrder(cmp) | Компаратор, обратный естественному порядку или переданному компаратору | Эквивалент Comparator.reverseOrder() |
emptyList(), singletonList(obj) | Неизменяемый пустой список / список из одного элемента | add() — UnsupportedOperationException |
unmodifiableList(list) | Представление только для чтения | Изменения исходного списка видны через обёртку |
synchronizedList(list) | Обёртка с синхронизацией каждого метода | Перебор нужно вручную заключать в synchronized (list) { ... } |
checkedList(list, type) | Проверяет тип каждого добавляемого элемента | Неверный тип — ClassCastException в момент вставки |
Почти у каждого метода есть аналоги для других типов коллекций: emptySet(), emptyMap(), unmodifiableSet(), synchronizedMap(), checkedMap() и так далее. Примеры ниже используют Arrays.asList() — это короткий способ получить список фиксированного размера, который поддерживает set(), а значит, и все алгоритмы, переставляющие элементы.
Какие списки можно передавать в Collections
sort(), reverse(), shuffle(), swap(), fill() и rotate() меняют список через set(), поэтому работают с ArrayList, LinkedList и Arrays.asList(). Списки из List.of(), nCopies() и unmodifiableList() на любую перестановку отвечают UnsupportedOperationException — сначала сделайте копию: new ArrayList<>(List.of(...)). Почему Arrays.asList() связан с исходным массивом, показано в уроке Backed Collections.
Метод Collections.sort()
Метод Collections.sort(list) сортирует список на месте в естественном порядке (элементы должны реализовывать Comparable), а Collections.sort(list, comparator) — в порядке, заданном компаратором. Сортировка стабильна: равные элементы сохраняют исходный взаимный порядок. Для сортировки по убыванию передают Collections.reverseOrder().
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class SortCollections {
public static void main(String[] args) {
List<String> list = Arrays.asList("красный", "синий", "зеленый");
System.out.println("Перед сортировкой: " + list);
Collections.sort(list);
System.out.println("После сортировки: " + list);
Collections.sort(list, Collections.reverseOrder());
System.out.println("После обратной сортировки: " + list);
}
} Вывод:
Перед сортировкой: [красный, синий, зеленый]
После сортировки: [зеленый, красный, синий]
После обратной сортировки: [синий, красный, зеленый] Строки сравниваются по кодам символов UTF-16, а не по словарю, — поэтому заглавные буквы и буква «ё» могут оказаться не там, где ожидаешь (разбор — в уроке Comparable и Comparator). С Java 8 Collections.sort(list) просто вызывает list.sort(null); чем отличаются эти записи, объяснено в уроке List и ArrayList. Как собрать компаратор из нескольких полей, показано в разделе Comparator.comparing, thenComparing, reversed.
Метод Collections.binarySearch()
Метод Collections.binarySearch(list, key) ищет элемент в отсортированном списке бинарным поиском и возвращает его индекс. Если элемента нет, результат отрицательный: -(точка вставки) - 1, где точка вставки — позиция, на которой элемент стоял бы в отсортированном списке. Для списков с произвольным доступом (ArrayList) поиск занимает O(log n).
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class BinarySearchExample {
public static void main(String[] args) {
List<String> list = Arrays.asList("красный", "синий", "зеленый");
Collections.sort(list);
System.out.println(list);
System.out.println(Collections.binarySearch(list, "красный"));
System.out.println(Collections.binarySearch(list, "черный"));
}
} Вывод:
[зеленый, красный, синий]
1
-4 «черный» встал бы после «синий», в позицию 3, поэтому метод вернул -(3) - 1 = -4. Из отрицательного результата точку вставки получают так: -result - 1.
Что выведет этот код, если забыть отсортировать список?
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class UnsortedBinarySearch {
public static void main(String[] args) {
List<String> list = Arrays.asList("красный", "синий", "зеленый");
System.out.println(list.indexOf("зеленый"));
System.out.println(Collections.binarySearch(list, "зеленый"));
}
} Вывод:
2
-1 Бинарный поиск сравнил «зеленый» со средним элементом «синий», решил, что искать нужно левее, потом сравнил с «красный» и снова ушёл влево — до элемента в позиции 2 он так и не дошёл. Java не проверяет, отсортирован ли список, поэтому ошибка проявляется только неверным результатом. То же правило действует для компаратора: если список отсортирован через Collections.reverseOrder(), искать нужно с тем же компаратором — Collections.binarySearch(list, key, Collections.reverseOrder()).
Методы reverse(), shuffle() и swap()
Метод Collections.reverse(list) переворачивает порядок элементов списка, Collections.shuffle(list) переставляет их в случайном порядке, а Collections.swap(list, i, j) меняет местами два элемента с индексами i и j. Все три метода изменяют сам список и ничего не возвращают.
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class ReverseShuffleExample {
public static void main(String[] args) {
List<String> list = Arrays.asList("красный", "синий", "зеленый", "черный");
System.out.println("Исходный список: " + list);
Collections.reverse(list);
System.out.println("После reverse(): " + list);
Collections.swap(list, 0, 3);
System.out.println("После swap(0, 3): " + list);
Collections.shuffle(list);
System.out.println("После shuffle(): " + list);
}
} Вывод (последняя строка при каждом запуске своя):
Исходный список: [красный, синий, зеленый, черный]
После reverse(): [черный, зеленый, синий, красный]
После swap(0, 3): [красный, зеленый, синий, черный]
После shuffle(): [синий, черный, красный, зеленый] По умолчанию shuffle() берёт внутренний генератор случайных чисел. Если нужно, чтобы перемешивание повторялось от запуска к запуску (тесты, воспроизведение бага, одинаковая колода в игре), передайте свой генератор с фиксированным начальным значением: Collections.shuffle(list, new Random(42)) — для одного и того же исходного списка порядок всегда будет одинаковым. Вызов Collections.swap(list, 0, 4) на списке из четырёх элементов бросит IndexOutOfBoundsException.
Методы fill() и nCopies()
Метод Collections.fill(list, obj) заменяет каждый элемент списка значением obj, но не добавляет новых элементов. Чтобы создать список из n одинаковых значений, используют Collections.nCopies(n, obj): он возвращает неизменяемый список, который обычно сразу копируют в ArrayList.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CollectionsFillExample {
public static void main(String[] args) {
List<String> colors = Arrays.asList("красный", "синий", "зеленый");
Collections.fill(colors, "черный");
System.out.println(colors);
List<String> empty = new ArrayList<>();
Collections.fill(empty, "черный");
System.out.println(empty);
List<String> filled = new ArrayList<>(Collections.nCopies(3, "черный"));
filled.add("белый");
System.out.println(filled);
}
} Вывод:
[черный, черный, черный]
[]
[черный, черный, черный, белый] Пустой список после fill() остался пустым: заполнять нечего. Список из nCopies() без копирования не изменить — add() бросит UnsupportedOperationException, а nCopies(-1, "x") — IllegalArgumentException. Все n позиций ссылаются на один и тот же объект, поэтому для изменяемых объектов (например, nCopies(3, new StringBuilder())) изменение одного элемента видно во всех.
Методы Collections.max() и min()
Методы Collections.max(collection) и Collections.min(collection) возвращают наибольший и наименьший элемент любой коллекции в естественном порядке, а перегрузки с компаратором — по заданному правилу. Коллекция перебирается целиком за O(n), сортировать её заранее не нужно. Для пустой коллекции оба метода бросают NoSuchElementException.
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class CollectionsMinMaxExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(2, 2, 5, 8, 9);
System.out.println(Collections.max(numbers));
System.out.println(Collections.min(numbers));
List<String> words = Arrays.asList("кот", "собака", "ёж");
System.out.println(Collections.max(words, Comparator.comparingInt(String::length)));
System.out.println(Collections.max(words));
}
} Вывод:
9
2
собака
ёж Последняя строка удивляет: «ёж» оказался «больше» «собаки», потому что код буквы «ё» (U+0451) больше кода «с» (U+0441). Вызов Collections.max(new ArrayList<Integer>()) завершится исключением java.util.NoSuchElementException — если коллекция может быть пустой, проверьте isEmpty() заранее или используйте stream().max(cmp), который возвращает Optional.
Метод Collections.copy()
Метод Collections.copy(dest, src) записывает элементы списка src поверх первых позиций списка dest; остальные элементы dest не меняются. Размер dest должен быть не меньше размера src — иначе метод бросает IndexOutOfBoundsException. Ёмкость ArrayList здесь не считается, важен только size().
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CollectionsCopyExample {
public static void main(String[] args) {
List<Integer> src = Arrays.asList(1, 2, 3);
List<Integer> dest = Arrays.asList(4, 5, 6, 7);
Collections.copy(dest, src);
System.out.println(dest);
List<Integer> empty = new ArrayList<>(10);
Collections.copy(empty, src);
}
} Вывод:
[1, 2, 3, 7]
Exception in thread "main" java.lang.IndexOutOfBoundsException: Source does not fit in dest Список new ArrayList<>(10) только выглядит вместительным: его size() равен 0. Если нужна независимая копия списка, а не перезапись существующего, пишут new ArrayList<>(src) или неизменяемый List.copyOf(src) (Java 10).
Метод Collections.rotate()
Метод Collections.rotate(list, distance) циклически сдвигает элементы списка: элемент с индексом i переходит на позицию (i + distance) % size. Положительное distance сдвигает элементы вправо, отрицательное — влево, а элементы, вышедшие за конец списка, появляются в начале.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsRotateExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>(List.of("a", "b", "c", "d", "e"));
System.out.println(list);
Collections.rotate(list, 2);
System.out.println(list);
Collections.rotate(list, -1);
System.out.println(list);
List<String> letters = new ArrayList<>(List.of("a", "b", "c", "d", "e"));
Collections.rotate(letters.subList(1, 4), -1);
System.out.println(letters);
}
} Вывод:
[a, b, c, d, e]
[d, e, a, b, c]
[e, a, b, c, d]
[a, c, d, b, e] Сдвиг больше размера списка не ошибка: rotate(list, 7) для пяти элементов даёт тот же результат, что rotate(list, 2). Последний пример — приём из документации JDK: rotate() для подсписка переносит элемент "b" с позиции 1 на позицию 3, не трогая остальную часть списка.
Методы frequency() и disjoint()
Метод Collections.frequency(collection, obj) возвращает, сколько раз элемент встречается в коллекции, сравнивая элементы через equals(). Метод Collections.disjoint(c1, c2) возвращает true, если у двух коллекций нет ни одного общего элемента. Оба метода работают с любой Collection, не только со списками.
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class CollectionsFrequencyExample {
public static void main(String[] args) {
List<String> colors = Arrays.asList("красный", "синий", "красный", null);
System.out.println(Collections.frequency(colors, "красный"));
System.out.println(Collections.frequency(colors, "Красный"));
System.out.println(Collections.frequency(colors, null));
List<String> warm = List.of("красный", "оранжевый");
List<String> cold = List.of("синий", "голубой");
System.out.println(Collections.disjoint(colors, warm));
System.out.println(Collections.disjoint(warm, cold));
}
} Вывод:
2
0
1
false
true «Красный» с заглавной буквы не найден: equals() учитывает регистр. Для собственных классов frequency() считает правильно, только если в них переопределён метод equals(). Метод проходит коллекцию целиком, поэтому подсчёт частоты каждого элемента в цикле занимает O(n²) — для больших данных удобнее HashMap со счётчиками.
Создание списков: addAll(), emptyList(), singletonList()
Метод Collections.addAll(collection, e1, e2, ...) добавляет перечисленные элементы в существующую коллекцию и возвращает true, если она изменилась. Методы emptyList() и singletonList(obj) возвращают неизменяемые списки из нуля и одного элемента — их используют как возвращаемое значение, чтобы не создавать лишний ArrayList.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsCreateExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
System.out.println(Collections.addAll(list, "a", "b", "c"));
System.out.println(list);
List<String> none = Collections.emptyList();
List<String> one = Collections.singletonList("a");
System.out.println(none + " " + one);
one.add("b");
}
} Вывод:
true
[a, b, c]
[] [a]
Exception in thread "main" java.lang.UnsupportedOperationException Для Set метод addAll() вернёт false, если все элементы там уже были. Начиная с Java 9 вместо emptyList() и singletonList() часто пишут List.of() и List.of(obj); разница в том, что singletonList(null) допустим, а List.of(null) бросает NullPointerException.
Обёртки checkedList(), unmodifiableList(), synchronizedList()
Обёртки класса Collections возвращают новое представление того же списка с дополнительным поведением: checkedList() проверяет тип каждого добавляемого элемента, unmodifiableList() запрещает изменения, synchronizedList() синхронизирует методы. Данные не копируются — обёртка работает с исходным списком, поэтому изменения, сделанные в обход неё, видны через обёртку.
Проверяемая обёртка (checkedList(), checkedCollection(), checkedMap()) нужна там, где generics не защищают: код с raw-типами или старая библиотека может положить в List<String> число, и ошибка проявится далеко от места вставки. Обёртка бросает исключение сразу:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CheckedListExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("one", "two"));
List<String> checked = Collections.checkedList(names, String.class);
addRaw(names, 10); // попадёт в список без проверки
System.out.println(names);
addRaw(checked, 20); // ClassCastException
}
// имитирует старый код без generics
@SuppressWarnings({"rawtypes", "unchecked"})
static void addRaw(List list, Object value) {
list.add(value);
}
} Вывод:
[one, two, 10]
Exception in thread "main" java.lang.ClassCastException: Attempt to insert class java.lang.Integer element into collection with element type class java.lang.String Чем unmodifiableList() отличается от List.of() и List.copyOf(), сравнивается в таблице раздела Неизменяемые коллекции, а когда вместо synchronizedList() и synchronizedMap() лучше взять CopyOnWriteArrayList или ConcurrentHashMap — в разделе Потокобезопасные коллекции Java.
Новое в Java 21
Вместе с интерфейсами SequencedCollection, SequencedSet и SequencedMap (JEP 431) в класс Collections добавлены обёртки unmodifiableSequencedCollection(), unmodifiableSequencedSet() и unmodifiableSequencedMap(). Они сохраняют методы getFirst(), getLast() и reversed(), которых нет у обычной unmodifiableCollection(). На Java 17 этих методов нет.
Часто задаваемые вопросы
Как найти самый частый элемент списка с помощью Collections?
Комбинацией max() и frequency(): Collections.max(list, Comparator.comparingInt(x -> Collections.frequency(list, x))). Для [a, b, a, c, b, a] результат — a; при равной частоте вернётся элемент, встретившийся первым. Это O(n²), поэтому для больших списков частоты считают за один проход в HashMap. Как работает frequency() — в разделе Методы frequency() и disjoint().
Как перевернуть список, не изменяя исходный?
Collections.reverse() меняет сам список, поэтому сначала делают копию: List<String> copy = new ArrayList<>(list); Collections.reverse(copy);. В Java 21 есть list.reversed() — он возвращает представление в обратном порядке без копирования, но изменения через него попадают в исходный список.
Зачем нужен Collections.emptyList(), если можно вернуть new ArrayList<>()?
emptyList() возвращает один общий неизменяемый объект, поэтому не тратит память при каждом вызове и защищает от случайного изменения: вызывающий код получит UnsupportedOperationException при add(). Если вызывающему коду нужно наполнять результат, возвращайте new ArrayList<>().
Работают ли методы Collections с массивами и Map?
С массивами — нет: для них есть аналогичный класс Arrays (Arrays.sort(), Arrays.binarySearch(), Arrays.fill()), а массив объектов можно обернуть в список через Arrays.asList(). Алгоритмы вроде sort() и shuffle() к Map не применяются, но для отображений есть обёртки: unmodifiableMap(), synchronizedMap(), checkedMap(), emptyMap(), singletonMap().
Комментарии