Получить доступ

Чем Comparable отличается от Comparator и что значит согласованность порядка с equals?

MiddleКоллекции
Ещё спрашивают так:
  • Как сортировать объекты в Java?
  • Comparable или Comparator — в чём разница, и что значит требование согласованности порядка с equals?
Коротко

Comparable — естественный порядок, который задаёт сам класс (compareTo), один. Comparator — внешний порядок, их может быть сколько угодно, комбинируются через comparing().thenComparing(). Согласованность с equals: compareTo == 0 ровно тогда, когда equals == true.

  • String, Integer, LocalDate реализуют Comparable
  • Comparator — когда класс чужой или нужны разные сортировки
  • Несогласованный порядок ломает TreeSet и TreeMap: «равные» по сравнению элементы теряются

А ещё Не сравнивать вычитанием a - b — переполнение; Integer.compare

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Comparable<T> реализует сам класс: int compareTo(T other) — отрицательное, ноль или положительное. Это его естественный порядок, используемый Collections.sort, TreeSet, Arrays.sort без компаратора.

Comparator<T> — отдельный объект сравнения. Передаётся в sort, TreeMap, stream.sorted, max. Удобные фабрики:

users.sort(Comparator.comparing(User::lastName)
                     .thenComparing(User::firstName)
                     .thenComparing(User::age, Comparator.reverseOrder()));

nullsFirst, nullsLast, reversed().

Согласованность с equals. Порядок согласован, если a.compareTo(b) == 0 тогда и только тогда, когда a.equals(b). Сортированные коллекции (TreeSet, TreeMap) определяют одинаковость только через сравнение. Если компаратор считает равными разные по equals объекты (сравнение только по фамилии), TreeSet молча отбросит второго Петрова, хотя HashSet хранил бы обоих. Известный пример несогласованности в JDK — BigDecimal: 2.0 и 2.00 не равны по equals, но compareTo даёт 0.

Ловушки: сравнение через вычитание (return a.x - b.x) переполняется для больших значений — Integer.compare; контракт требует транзитивности и антисимметричности, нарушение даёт IllegalArgumentException: Comparison method violates its general contract в TimSort.

Что хотят услышать

  • Различие: внутренний единственный порядок против внешних.
  • Комбинирование компараторов.
  • Согласованность с equals и последствия для TreeSet.
  • Ловушка с вычитанием.

Частые ошибки

  • Сравнивать вычитанием.
  • Делать TreeSet с компаратором по неуникальному полю.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✗Comparator реализуют внутри класса, Comparable — снаружи
  • ✗Comparable сравнивает строки, Comparator — числа
  • ✓Comparable задаёт естественный порядок внутри класса (compareTo); Comparator — внешний порядок (compare), их может быть много
  • ✗Это одно и то же

Что спросят дальше

  • Как безопасно сравнивать int?
  • Что значит comparator consistent with equals?
  • Почему BigDecimal — классический пример несогласованного compareTo?
  • Как правильно собрать компаратор по нескольким полям с nulls?

Разбор темы

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем