0Pricing
Java Academy · Урок

Сортировка по нескольким ключам с 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 sorted

Comparator.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 — локальная установка не требуется.

Все уроки этого курса

  1. Интерфейс Comparable
  2. Сортировка с Comparator и лямбда-выражениями
  3. Сортировка по нескольким ключам с thenComparing
  4. Сортировка массивов и коллекций на практике
← Назад к Java Academy