Вопрос с собеседования · Java

Чем 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

Подробно

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 задаёт порядок снаружи класса, а Comparator — единственный естественный порядок внутри него

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

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

Разбор темы

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

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования