Pull to refresh

Выбор между Comparator и Comparable

Reading time 3 min
Views 13K

Для реализации сортировки требуется, чтобы сортируемые объекты можно было сравнивать между собой на больше-меньше. Иначе говоря, чтобы было определено правило, которое позволит для любых двух объектов указать, какой из них в рамках данного контекста идет раньше, а какой позже.

В java эти правила определяются на уровне классов, к которым принадлежат объекты. Для примера возьмем класс для описания счета пользователя:

UserAccount {
  currency
  value
  updatedTimestamp
}

В зависимости от контекста сравнение счетов пользователя может происходить по разным правилам, например:

  • в приложении пользователь видит счета, отсортированные по currency, потом по value;

  • в админке счета всех пользователей отсортированы по дате изменения.

Для реализации сравнения на больше-меньше в java предусмотрено две возможности:

  • UserAccount реализует интерфейс Comparable<UserAccount> В этом случае два объекта получают возможность сравнения между собой: acc1.compareTo(acc2)

  • Создается отдельный класс, реализующий интерфейс Comparator<UserAccount>, и дальше объект этого класса может сравнить между собой два объекта исходного класса: userAccountComparator.compare(acc1, acc2)

Очевидно, что в некоторых случаях выбора между Comparable и Comparator нет. Если исходный класс нельзя модифицировать, или если требуются разные правила сравнения, то придется использовать Comparator. В остальных случаях, технически, можно использовать как Comparator, так и Comparable.

Согласно документации использование Comparable возможно, если для сравнения используется естественный порядок (class's natural ordering). По ссылке есть представление разработчиков, что такое естественный порядок для стандартных классов (String, Date). Мне не удалось выяснить, что такое естественный порядок в общем случае. Выглядит, что это интуитивное представление разработчика о порядке в данном контексте. При этом даже для стандартных классов порядок может быть не интуитивен (в каком порядке должны идти значения BigDecimal с разной точностью, например 4.0 и 4.00?). Практика показывает, что "естественность" правил различается в понимании разных разработчиков и контекстов. Для меня необходимость опоры на интуицию является аргументом против использования Comparable.

При написании кода приходится лавировать между явностью и неявностью. Явность хороша тем, что для понимания участка кода требуется рассмотреть лишь узкий контекст его использования. В то же время неявность позволяет использовать одинаковые правила для всех участков кода. Разберем на примере: разработчику требуется отсортировать список счетов. Правила сравнения можно указать:

  • неявно: Arrays.sort(accountsList)

  • явно: Arrays.sort(accountsList, accountByValueComparator)

Чтобы разработчику узнать правила сортировки в первом случае ему нужно будет посмотреть, как реализован метод compareTo у класса UserAccount, а во втором - у accountByValueComparator. Здесь разницы нет. Но посмотрим на обратную задачу. Пусть разработчик видит перед собой код

UserAccount implements Comparable<UserAccount> {
  @Override
  public int compareTo(UserAccount other) { .. }
}

Как определить, где используется объявленный метод compareTo ? Если искать по использованию метода, то потребуется включить в поиск и метод суперкласса Comparable#compareTo, так как именно он будет найден в Arrays.sort(). Но метод суперкласса используется также в огромном количестве других мест внутри кода jdk, и найти таким образом его будет сложно. Я не нашел способов искать такие использования методов кроме как ручным перебором: ищем все включения класса UserAccount и ниже по стеку обращаем внимание на все Arrays.sort(), stream.sorted() и тд.

В случае явной передачи компаратора найти его использования элементарно. Я считаю это аргументом за использование компаратора. (Еще одним примером сложностей с поиском неявных использований может служить equals/hashCode, но альтернативы в виде "Equalator"-а для них нет).

Резюмируя - в большинстве случаев аргументы за использование Comparator перевешивают аргументы за использование Comparable.

Tags:
Hubs:
+2
Comments 16
Comments Comments 16

Articles