Сортировка по нескольким ключам с thenComparing
Объединяйте компараторы с помощью thenComparing, чтобы сортировать по нескольким полям в порядке приоритета.
«Сортировка по нескольким ключам с thenComparing» — бесплатный урок Java Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Сортировка по нескольким ключам
thenComparing() объединяет Comparator для сортировки по нескольким полям. Сначала выполняется основная сортировка; при равенстве — вторичная и так далее.
Базовое использование thenComparing
Используйте thenComparing(), чтобы разрешить совпадения после применения основного Comparator.
import java.util.*;
record Student(String name, int grade, double gpa) {}
List<Student> students = List.of(
new Student("Alice", 10, 3.9),
new Student("Bob", 11, 3.7),
new Student("Charlie", 10, 3.8),
new Student("Diana", 11, 3.7)
);
Comparator<Student> sort = Comparator.comparingInt(Student::grade)
.thenComparingDouble(Student::gpa).reversed()
.thenComparing(Student::name);
students.stream().sorted(sort).forEach(System.out::println);Сортировка в три уровня
Объединяйте столько уровней thenComparing, сколько требуется.
record Employee(String dept, String team, String name, double salary) {}
List<Employee> employees = List.of(
new Employee("Eng", "Backend", "Alice", 95000),
new Employee("Eng", "Backend", "Bob", 92000),
new Employee("Eng", "Frontend","Charlie", 88000),
new Employee("Mkt", "Growth", "Diana", 75000)
);
Comparator<Employee> sort = Comparator
.comparing(Employee::dept)
.thenComparing(Employee::team)
.thenComparing(Comparator.comparingDouble(Employee::salary).reversed());
employees.stream().sorted(sort)
.forEach(e -> System.out.printf("%s > %s > %s: $%.0f%n",
e.dept(), e.team(), e.name(), e.salary()));thenComparing с Comparator
Используйте перегруженный вариант, принимающий Comparator, если для вторичных ключей требуется обратный или пользовательский порядок.
Comparator<Employee> sort = Comparator
.comparing(Employee::dept)
.thenComparing(Comparator.comparing(Employee::salary).reversed()); // highest paid first
// equivalent: .thenComparingDouble(Employee::salary, Comparator.reverseOrder())Сортировка с пустыми полями
Объединяйте thenComparing с nullsLast/nullsFirst, чтобы корректно обрабатывать необязательные поля.
record Product(String name, String category, Double rating) {}
Comparator<Product> sort = Comparator
.comparing(Product::category, Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Product::rating, Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(Product::name);Динамический выбор ключа сортировки
Выбирайте ключ сортировки во время выполнения на основе предпочтений пользователя.
static Comparator<Product> sortBy(String key) {
return switch (key) {
case "price" -> Comparator.comparingDouble(Product::price);
case "name" -> Comparator.comparing(Product::name);
case "stock" -> Comparator.comparingInt(Product::stock).reversed();
default -> Comparator.comparingDouble(Product::price);
};
}
String userChoice = "stock";
products.stream().sorted(sortBy(userChoice)).forEach(System.out::println);thenComparing с функцией извлечения
Перегруженный вариант thenComparing(Function<T,U>) извлекает ключ для вторичной сортировки, поддерживающий естественное сравнение, без явного Comparator.
record Event(String type, int year, int month, int day) {}
Comparator<Event> chronological = Comparator
.comparing(Event::type)
.thenComparingInt(Event::year)
.thenComparingInt(Event::month)
.thenComparingInt(Event::day);Сортировка коллекций записей
Записи с методами доступа без проблем работают со ссылками на методы в цепочках Comparator.
record Order(String customerId, String status, double total, java.time.LocalDate date) {}
List<Order> orders = loadOrders();
Comparator<Order> byCustomerThenDate = Comparator
.comparing(Order::customerId)
.thenComparing(Order::date, Comparator.reverseOrder()); // newest first per customer
orders.stream().sorted(byCustomerThenDate)
.collect(Collectors.groupingBy(Order::customerId))
.forEach((cust, ords) -> {
System.out.println("Customer: " + cust);
ords.forEach(o -> System.out.println(" " + o.date() + " $" + o.total()));
});Гарантия стабильной сортировки
List.sort() и Arrays.sort() для объектов в Java используют стабильную сортировку слиянием. Элементы, считающиеся равными при сравнении, сохраняют исходный взаимный порядок.
// Stable sort example:
// First sort by name, then by dept
// Equal dept elements preserve the name order from previous sort
List<Employee> sorted = employees.stream()
.sorted(Comparator.comparing(Employee::name)) // 1st pass
.sorted(Comparator.comparing(Employee::dept)) // 2nd pass (stable!)
.collect(Collectors.toList());
// Within each dept, employees remain alphabetically sortedComparator.comparing с Comparator ключа
Двухаргументная форма Comparator.comparing(keyExtractor, keyComparator) позволяет указать и ключ, и порядок его сортировки.
// Sort by name length, then alphabetically within same length
Comparator<String> c = Comparator
.comparing(String::length, Integer::compare)
.thenComparing(Comparator.naturalOrder());
List<String> words = new ArrayList<>(List.of("fig","apple","kiwi","banana","pea"));
words.sort(c);
System.out.println(words); // [fig, pea, kiwi, apple, banana]Реальные сценарии: результаты поиска рейсов
Сортировка результатов поиска рейсов по цене, затем по длительности и затем по времени вылета.
record Flight(String airline, double price, int durationMin, java.time.LocalTime departs) {}
Comparator<Flight> bestFlight = Comparator
.comparingDouble(Flight::price)
.thenComparingInt(Flight::durationMin)
.thenComparing(Flight::departs);Сбор отсортированных результатов
Объединение сортировки по нескольким ключам со сборщиками для создания отсортированных групп или отображений.
Map<String, List<Employee>> byDept = employees.stream()
.sorted(Comparator.comparing(Employee::dept)
.thenComparing(Comparator.comparingDouble(Employee::salary).reversed()))
.collect(Collectors.groupingBy(Employee::dept,
LinkedHashMap::new, // preserve insertion (sorted) order
Collectors.toList()));
byDept.forEach((dept, emps) -> {
System.out.println(dept + ":");
emps.forEach(e -> System.out.println(" " + e.name() + " $" + e.salary()));
});Быстрая проверка
Вам нужно отсортировать сотрудников сначала по отделу в алфавитном порядке, а затем по убыванию зарплаты. Какая цепочка правильна?
Итоги: сортировка по нескольким ключам с thenComparing
Основные выводы:
- thenComparing() добавляет вторичные ключи сортировки, которые применяются, когда первичные ключи равны
- Объединяйте столько уровней thenComparing, сколько необходимо
- Используйте thenComparing(Comparator) вместе с reversed(), когда вторичный ключ нужно сортировать по убыванию
- Объединяйте их с nullsFirst/nullsLast для необязательных полей
- Метод List.sort() в Java стабилен — равные элементы сохраняют исходный порядок
- Динамический выбор сортировки: используйте switch, чтобы во время выполнения вернуть нужный Comparator
Часто задаваемые вопросы
Урок «Сортировка по нескольким ключам с thenComparing» бесплатный?
Да — полный текст урока «Сортировка по нескольким ключам с thenComparing» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «Сортировка по нескольким ключам с thenComparing»?
Объединяйте компараторы с помощью thenComparing, чтобы сортировать по нескольким полям в порядке приоритета. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Сортировка по нескольким ключам с thenComparing»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Интерфейс Comparable
- Сортировка с Comparator и лямбда-выражениями
- Сортировка по нескольким ключам с thenComparing
- Сортировка массивов и коллекций на практике