JavaRush /Курсы /JAVA 25 SELF /Интерфейс Comparator: создание, использование

Интерфейс Comparator: создание, использование

JAVA 25 SELF
29 уровень , 3 лекция
Открыта

1. Введение

В жизни редко бывает достаточно одного способа сравнивать объекты. Представьте, что у вас есть список пользователей: иногда вы хотите сортировать их по имени, иногда — по возрасту, а иногда — по длине фамилии. Или у вас есть класс, который вообще не вы — автор, и добавить в него compareTo не получится. Именно для таких случаев в Java существует интерфейс Comparator.

Когда Comparable не хватает

  • Класс нельзя менять (например, он из сторонней библиотеки).
  • Нужно несколько способов сортировки (по разным полям).
  • Хотите отделить логику сравнения от самого класса (например, сортировать по-разному в разных частях программы).

Аналогия
Если Comparable — это встроенный «естественный порядок» объекта, то Comparator — это внешний судья, который может оценивать ваши объекты по любым критериям: сегодня по имени, завтра — по возрасту, послезавтра — по длине имени.

2. Интерфейс Comparator: синтаксис и контракт

Объявление интерфейса

public interface Comparator<T> {
    int compare(T o1, T o2);
}

Метод compare должен возвращать:

  • Отрицательное число, если первый объект «меньше» второго.
  • 0, если они равны.
  • Положительное число, если первый «больше» второго.

Контракт тот же, что и у Comparable, только теперь сравниваются два объекта, а не «текущий» и «другой» через compareTo.

Пример: компаратор для сортировки по фамилии

Допустим, у нас есть класс Person:

public class Person {
    private String firstName;
    private String lastName;
    private int age;

    // Конструктор и геттеры
    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
}

Создадим компаратор, который будет сортировать по фамилии:

import java.util.Comparator;

public class LastNameComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return a.getLastName().compareTo(b.getLastName());
    }
}

Заметка: метод compareTo у строк (String) сравнивает их в алфавитном порядке.

3. Использование Comparator: сортировка коллекций

Сортировка с помощью компаратора

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Анна", "Костецкая", 25));
        people.add(new Person("Борис", "Новак", 20));
        people.add(new Person("Виктория", "Белл", 22));

        // Сортировка по фамилии
        Collections.sort(people, new LastNameComparator());

        for (Person p : people) {
            System.out.println(p.getLastName() + " " + p.getFirstName());
        }
    }
}

Результат:

Новак Борис
Костецкая Анна
Белл Виктория

Сортировка по возрасту с помощью компаратора

Даже если класс уже реализует Comparable по имени, можно создать отдельный компаратор — по возрасту:

public class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return Integer.compare(a.getAge(), b.getAge());
    }
}

И использовать аналогично:

Collections.sort(people, new AgeComparator());

Результат:

Борис Новак (20)
Виктория Белл (22)
Анна Костецкая (25)

Пример: выбор компаратора «на лету»

Collections.sort(people, new LastNameComparator()); // По фамилии
Collections.sort(people, new AgeComparator());      // По возрасту

4. Анонимные классы и лямбда-выражения

Компараторы можно создавать «на лету», не объявляя отдельных классов.

Анонимный класс

Collections.sort(people, new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return a.getFirstName().compareTo(b.getFirstName());
    }
});

Лямбда-выражение

Collections.sort(people, (a, b) -> a.getFirstName().compareTo(b.getFirstName()));

Или ещё короче с методом списка List.sort:

people.sort((a, b) -> a.getFirstName().compareTo(b.getFirstName()));
  • Анонимные классы — старый способ, громоздко.
  • Лямбда — современно и компактно.

5. Примеры: сортировка по разным критериям

Сортировка по длине фамилии

Comparator<Person> byLastNameLength = (a, b) ->
        Integer.compare(a.getLastName().length(), b.getLastName().length());
people.sort(byLastNameLength);

Сортировка по возрасту, потом по имени (многоуровневая)

Comparator<Person> byAgeThenName = (a, b) -> {
    int cmp = Integer.compare(a.getAge(), b.getAge());
    if (cmp != 0) return cmp;
    return a.getFirstName().compareTo(b.getFirstName());
};
people.sort(byAgeThenName);

Использование компаратора для поиска (пример)

Компаратор применим не только к сортировке, но и к поиску в отсортированных коллекциях:

// people должны быть отсортированы по возрасту!
Person key = new Person("?", "?", 22);
int idx = Collections.binarySearch(people, key, new AgeComparator());
if (idx >= 0) {
    System.out.println("Найден человек с возрастом 22: " + people.get(idx));
}

6. Best practices и особенности работы с Comparator

Не нарушайте контракт

  • Если compare(a, b) возвращает 0, то compare(b, a) тоже должен вернуть 0.
  • Если compare(a, b) > 0, то compare(b, a) < 0.
  • Учитывайте возможные null-значения (см. ниже).

Не забывайте про equals и hashCode

Хотя компараторы и сравнивают объекты «по-своему», для структур наподобие TreeSet или при поиске ключей в TreeMap важно, чтобы логика сравнения по компаратору была согласована с equals. Иначе можно получить неожиданные результаты: два разных объекта считаются равными по компаратору, но не равны по equals.

Сортировка с учётом null

Если поля могут быть null, используйте «готовые» помощники:

Comparator<Person> byLastNameNullSafe = Comparator.comparing(
    Person::getLastName,
    Comparator.nullsLast(String::compareTo)
);
people.sort(byLastNameNullSafe);

7. Полезные нюансы

Таблица: Сравнение Comparable и Comparator

Comparable Comparator
Где реализуется? В самом классе В отдельном классе/лямбде
Метод
int compareTo(T o)
int compare(T o1, T o2)
Сколько вариантов? Только один «естественный» Сколько угодно, под любые нужды
Применение
Collections.sort(list)
Collections.sort(list, comp)
Можно для чужих классов? Нет Да

Пример: сортировка по убыванию

Инвертировать порядок можно вручную:

Comparator<Person> byAgeDesc = (a, b) -> Integer.compare(b.getAge(), a.getAge());
people.sort(byAgeDesc);

Или с помощью reversed():

Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);
people.sort(byAge.reversed());

8. Типичные ошибки при работе с Comparator

Ошибка №1: Нарушение контракта сравнения. Если вы забыли, что compare(a, b) и compare(b, a) должны быть противоположны по знаку, или возвращаете произвольные значения (например, просто разницу — a.getAge() - b.getAge(), что может переполниться), то результат будет непредсказуемым. Используйте Integer.compare, а не вычитание — так безопаснее.

Ошибка №2: Игнорирование null-значений. Если поля, по которым сравниваете, могут быть null, обязательно обработайте этот случай (например, через Comparator.nullsFirst/Comparator.nullsLast), иначе легко получить NullPointerException в самый неожиданный момент.

Ошибка №3: Нестабильные критерии сортировки. Если компаратор возвращает разные значения для одних и тех же объектов (например, использует случайное число или сильно изменяемое поле), сортировка может вести себя хаотично.

Ошибка №4: Несогласованность с equals. Если compare(a, b) == 0, но a.equals(b) равно false, коллекции вроде TreeSet и TreeMap могут работать не так, как вы ожидаете. Желательно, чтобы равенство по компаратору и по equals совпадали.

Ошибка №5: Сортировка без компаратора для чужих классов. Если вы пытаетесь сортировать объекты «чужого» класса без Comparable и без передачи Comparator, получите ошибку компиляции. Передавайте явный компаратор.

1
Задача
JAVA 25 SELF, 29 уровень, 3 лекция
Недоступна
Сортировка списка посетителей по возрасту на мероприятии 🧑‍🎓
Сортировка списка посетителей по возрасту на мероприятии 🧑‍🎓
1
Задача
JAVA 25 SELF, 29 уровень, 3 лекция
Недоступна
Упорядочивание участников по длине имени для бейджей 🏷️
Упорядочивание участников по длине имени для бейджей 🏷️
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Yokei Уровень 10
19 ноября 2025
"Или у вас есть класс, который вообще не вы — автор, и добавить в него compareTo не получится." — звучит немного странно, видимо дедлайн поджимал...