Сортировка и компараторы
Списки часто нужно упорядочить: имена по алфавиту, товары по цене, сотрудников по отделу и зарплате. Java умеет сортировать сама — нужно лишь объяснить, в каком порядке.
Естественный порядок
Числа и строки уже знают, как сравниваться друг с другом. Это называется естественным порядком. Метод Collections.sort() сортирует список по нему:
void main() {
List<Integer> numbers = new ArrayList<>(List.of(5, 2, 9, 1));
Collections.sort(numbers);
IO.println(numbers);
}Результат:
[1, 2, 5, 9]Строки сортируются по кодам символов: заглавные буквы идут раньше строчных.
Компаратор и обратный порядок
Компаратор (Comparator) — объект, который сравнивает два элемента и тем самым задаёт порядок. Метод списка sort() принимает компаратор. Готовый Comparator.reverseOrder() сортирует по убыванию:
void main() {
List<String> names = new ArrayList<>(List.of("Олег", "Анна", "Ира"));
names.sort(Comparator.reverseOrder());
IO.println(names);
}Результат:
[Олег, Ира, Анна]Comparator.comparing()
Для своих объектов компаратор удобно строить методом Comparator.comparing(): ему говорят, по какому значению сравнивать. Запись Product::price — ссылка на метод, короткий способ сказать «взять price() у каждого товара»:
record Product(String title, double price) {}
void main() {
List<Product> products = new ArrayList<>(List.of(
new Product("Чай", 4.5),
new Product("Кофе", 7.0),
new Product("Сок", 3.2)));
products.sort(Comparator.comparing(Product::price));
for (Product p : products) {
IO.println(p.title() + " " + p.price());
}
}Результат:
Сок 3.2
Чай 4.5
Кофе 7.0Добавьте .reversed() — и порядок станет обратным: Comparator.comparing(Product::price).reversed().
Несколько полей
Если значения первого поля совпали, второе поле задают методом thenComparing(). Здесь сотрудники отсортированы по отделу, а внутри отдела — по имени:
record Employee(String name, String department) {}
void main() {
List<Employee> staff = new ArrayList<>(List.of(
new Employee("Олег", "склад"),
new Employee("Анна", "продажи"),
new Employee("Ира", "склад"),
new Employee("Борис", "продажи")));
staff.sort(Comparator.comparing(Employee::department).thenComparing(Employee::name));
staff.forEach(e -> IO.println(e.department() + ": " + e.name()));
}Результат:
продажи: Анна
продажи: Борис
склад: Ира
склад: ОлегСвой естественный порядок: Comparable
Класс может сам задать свой естественный порядок, реализовав интерфейс Comparable и его метод compareTo(). Метод возвращает отрицательное число, если текущий объект меньше другого, ноль — если равны, и положительное — если больше:
record Version(int major, int minor) implements Comparable<Version> {
@Override
public int compareTo(Version other) {
if (major != other.major) {
return Integer.compare(major, other.major);
}
return Integer.compare(minor, other.minor);
}
}
void main() {
List<Version> versions = new ArrayList<>(List.of(
new Version(2, 1), new Version(1, 9), new Version(2, 0)));
Collections.sort(versions);
IO.println(versions);
}Результат:
[Version[major=1, minor=9], Version[major=2, minor=0], Version[major=2, minor=1]]Частая ошибка
Сортировка меняет сам список. Неизменяемый список из List.of() отсортировать нельзя:
void main() {
List<Integer> numbers = List.of(3, 1, 2);
Collections.sort(numbers);
}Результат:
Exception in thread "main" java.lang.UnsupportedOperationExceptionСначала сделайте изменяемую копию: List<Integer> sorted = new ArrayList<>(numbers); — и сортируйте её.