Comparator Java

Comparable<T> задаёт естественный порядок внутри самого типа. Comparator<T> задаёт внешний порядок и позволяет иметь несколько вариантов сортировки одних объектов.

record Candidate(String name, int score) {}
 
Comparator<Candidate> ranking =
        Comparator.comparingInt(Candidate::score)
                  .reversed()
                  .thenComparing(Candidate::name);
 
List<Candidate> ordered = candidates.stream()
        .sorted(ranking)
        .toList();

Результат сравнения

compare(a, b) или a.compareTo(b) возвращает:

  • отрицательное число, если a < b;
  • ноль, если элементы равны по порядку;
  • положительное число, если a > b.

Важен знак, а не конкретные -1 и 1. Нельзя сравнивать return a - b: разность может переполниться. Используются Integer.compare(a, b), Long.compare и фабрики Comparator.

Контракт порядка

Знак сравнения должен быть антисимметричным и транзитивным. Если compare(a, b) == 0, компаратор считает элементы эквивалентными по порядку. Желательно, чтобы это согласовывалось с equals, но API не требует этого всегда.

Особенно важно для TreeSet и TreeMap: элементы с результатом 0 считаются одним ключом, даже если equals возвращает false.

Полезные композиции

ЗадачаМетод
поле объектаcomparing
примитивное int-полеcomparingInt
второй критерийthenComparing
обратный порядокreversed
null в начале/концеnullsFirst, nullsLast
естественный порядокnaturalOrder

Comparator.nullsLast(Comparator.naturalOrder()) явно задаёт поведение для null; большинство обычных компараторов иначе бросит NullPointerException.

Связанные темы: Коллекции, Stream API, Equals и hashCode.

Вопросы для собеседования

  1. Comparable или Comparator? Comparable — один естественный порядок в классе, Comparator — внешние и альтернативные порядки.
  2. Почему нельзя возвращать a - b? Из-за переполнения.
  3. Что значит результат 0 в TreeSet? Элемент эквивалентен существующему и обычно не будет добавлен.
  4. Обязан ли compare быть согласован с equals? Не всегда, но несогласованность меняет поведение sorted set/map и должна быть осознанной.
  5. Как сравнивать по нескольким полям? Через thenComparing.
  6. Как обработать null? Явно обернуть comparator в nullsFirst/nullsLast.