Метод Collections.sort(List) – сортирует список по естественному порядку элементов (требует, чтобы элементы реализовали Comparable).
Метод List.sort(Comparator) – более современный подход: сортировка списка по заданному компаратору (можно передать лямбда-выражение).
Интерфейс Comparable – задает естественный порядок класса (метод compareTo).
Интерфейс Comparator – используется для создания внешних правил сортировки (можно определять разные сравнения для одних и тех же объектов).
Пояснение:
Перебор коллекций. Чтобы пройтись по всем элементам коллекции (например, напечатать их или применить к ним операцию), Java предлагает несколько способов:
Итератор (Iterator): Каждый коллекционный класс может предоставить итератор через метод iterator(). Итератор – это объект, реализующий интерфейс Iterator<E> с методами hasNext(), next() и опционально remove(). Преимущество итератора в том, что он позволяет безопасно удалять элементы во время обхода (методом remove() самого итератора). Использование:
List<String> names = new ArrayList<>(List.of("Anna", "Bob", "Charlie"));
Iterator<String> it = names.iterator();while (it.hasNext()) {
String name = it.next();
if (name.startsWith("B")) {
it.remove(); // удаляем имена, начинающиеся с "B"
} else {
System.out.println(name);
}
}// В результате в списке останутся только "Anna" и "Charlie"
Здесь мы обходим список через итератор и удаляем определенный элемент без ошибок. Если бы мы попытались удалить элемент через сам список внутри цикла for-each, получили бы ConcurrentModificationException. Итератор же разрешает удаление, поскольку он отслеживает изменения и находится в согласованном состоянии.
Цикл for-each: Это сокращенный способ перебора, появившийся с Java 5. Работает для любых Iterable (к которым относятся все коллекции). Код становится намного короче и понятнее:
for (String name : names) {
System.out.println(name);
}
Этот код под капотом все равно использует итератор, но синтаксически удобнее. Однако важно: во время такого перебора нельзя вызывать методы коллекции для удаления текущего элемента – нужно использовать итератор явно, как было показано выше, либо использовать removeIf(...) (появился с Java 8) для удаления по условию.
Стрим (Stream API): Начиная с Java 8, у интерфейса Collection есть метод stream(), возвращающий последовательность элементов (Stream). Стрим API позволяет выразить операции над коллекцией в функциональном стиле: фильтрация, преобразование, накопление и т.п. Например, если мы хотим выполнить действие для каждого элемента, можно использовать forEach терминальную операцию стрима:
names.stream()
.filter(n -> n.length() > 4) // оставить только имена длиной > 4
.forEach(n -> System.out.println(n.toUpperCase()));
В этом примере мы отфильтровали коллекцию и сразу в потоке напечатали отфильтрованные имена в верхнем регистре. Stream API удобен для массовой обработки данных: когда нужно выполнить цепочку операций (отфильтровать, преобразовать, собрать результаты и т.д.). Подробнее о стримах – в следующем блоке.
Сортировка коллекций. Сортировать имеет смысл упорядочиваемые коллекции, в первую очередь списки (List). Для сортировки Java предоставляет несколько механизмов:
Естественный порядок (Natural Ordering): Если элементы коллекции сами знают, как упорядочиваться, т.е. класс элементов реализует интерфейс Comparable<T>, можно воспользоваться простым методом Collections.sort(list). Он отсортирует список в соответствии с реализацией compareTo элементов. Например, класс String реализует Comparable (лексикографический порядок), Integer – тоже (числовой порядок). Таким образом, Collections.sort(listOfStrings) упорядочит список строк по алфавиту, а Collections.sort(listOfNumbers) – числа по возрастанию. Этот метод работает «на месте» – т.е. изменяет переданный список.
Comparator (пользовательский порядок): Бывают ситуации, когда естественного порядка нет или нужен другой критерий сравнения. Тогда используют объект, реализующий Comparator<T> – по сути, отдельный класс/функцию сравнения двух объектов. В класс Collections есть перегруженный метод sort(List, Comparator). Начиная с Java 8, удобнее вызывать прямо метод list.sort(comparator). Например:
List<String> names = new ArrayList<>(List.of("Anna", "Bob", "Charlie"));// Сортировка по длине строки с помощью лямбда-выражения (Comparator):
names.sort((a, b) -> Integer.compare(a.length(), b.length()));
System.out.println(names); // [Bob, Anna, Charlie] – отсортировано по длине
Здесь мы передали в sort лямбда-выражение, сравнивающее длины двух строк. Comparator можно создать разными способами; часто используют метод Comparator.comparing. Тот же пример можно написать так: names.sort(Comparator.comparingInt(String::length)); – это будет эквивалентно.
Интерфейс Comparable: Если вы пишете собственный класс и хотите, чтобы его экземпляры имели естественный порядок сортировки, реализуйте интерфейс Comparable<YourClass> и метод compareTo. Этот метод должен возвращать отрицательное число, ноль или положительное, если текущий объект меньше, равен или больше другого, соответственно. Например:
class Person implements Comparable<Person> {
String name;
int age;
// ... конструкторы, геттеры ...
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
}
Теперь список List<Person> можно сортировать вызовом Collections.sort(listOfPersons), и он упорядочится по возрасту (так мы определили в compareTo). Важно: сравнение должно быть согласовано с equals – объекты, считающиеся равными (equals == true), должны давать compareTo == 0.
Специальные случаи: Коллекции типа TreeSet или TreeMap требуют либо Comparable от элементов/ключей, либо Comparator при создании. После этого они сами поддерживают сортированность – каждый раз, когда вы добавляете элемент, он становится на правильное место в порядке.
Подведем итог: для однократной сортировки списка удобно использовать Collections.sort или List.sort (второй вариант предпочтительнее в новом коде). Для задания произвольного критерия сортировки – передать Comparator (чаще всего через лямбду). Если же порядок жестко закреплен за объектом (например, объекты имеют очевидный порядок, как числа, строки, даты), реализуйте Comparable прямо в классе.
