Рубрики
Без рубрики

Руководство по внедрению метода compareTo

Автор оригинала: Daniel Strmecki.

Руководство по внедрению метода compareTo

1. Обзор

Как Java-разработчики, нам часто приходится сортировать элементы, сгруппированные в коллекцию. Java позволяет нам реализовывать различные алгоритмы сортировки с помощью любого типа данных .

Например, мы можем сортировать строки в алфавитном порядке, обратном алфавитном порядке или на основе длины.

В этом учебнике мы изумим Сопоставимые интерфейс и его сравнить Для метод, который позволяет сортировку. Мы посмотрим на коллекции сортировки, которые содержат объекты как из основных, так и из пользовательских классов.

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

2. Сопоставимый интерфейс

Сопоставимые интерфейсные навязывает заказ на объекты каждого класса, который реализует его .

сравнить Для является единственным методом, определяемым Сопоставимые интерфейс. Его часто называют естественным методом сравнения.

2.1. Реализация сравнения

сравнить Для метод сравнивает текущий объект с объектом, отправленным в качестве параметра .

При его реализации мы должны убедиться, что метод возвращается:

  • Положительный integer, если текущий объект больше, чем объект параметра
  • Отрицательный integer, если текущий объект меньше параметра объекта
  • Ноль, если текущий объект равен объекту параметра

В математике мы называем это знаком или функцией signum:

Функция сигнума

2.2. Пример осуществления

Давайте посмотрим, как сравнить Для метод реализуется в основной Интегер класс:

@Override
public int compareTo(Integer anotherInteger) {
    return compare(this.value, anotherInteger.value);
}

public static int compare (int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

2.3. Сломанный шаблон вычитания

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

@Override
public int compareTo(BankAccount anotherAccount) {
    return this.balance - anotherAccount.balance;
}

Рассмотрим пример, когда мы ожидаем, что положительное сальдо счета будет больше отрицательного:

BankAccount accountOne = new BankAccount(1900000000);
BankAccount accountTwo = new BankAccount(-2000000000);
int comparison = accountOne.compareTo(accountTwo);
assertThat(comparison).isNegative();

Тем не менее, integer не достаточно большой, чтобы хранить разницу, тем самым давая нам неправильный результат. Конечно, в этом шаблон нарушается из-за возможного переполнения интеграторов и нуждается в .

Правильным решением является использование сравнения вместо вычитания. Мы также можем повторно использовать правильную реализацию из основных Интегер класс:

@Override
public int compareTo(BankAccount anotherAccount) {
    return Integer.compare(this.balance, anotherAccount.balance);
}

2.4. Правила осуществления

Для того, чтобы должным образом реализовать сравнить Для метод, мы должны соблюдать следующие математические правила:

  • sgn (x.compareTo(y)) – sgn(y.compareTo(x))
  • (x.compareTo(y) > 0 y.compareTo (z) > 0) подразумевает x.compareTo(z) > 0
  • x.compareTo(y) означает, что sgn (x.compareTo(z)))(y.compareTo(z))

Кроме того, настоятельно рекомендуется, хотя и не требуется, держать сравнить Для в соответствии с равняется метод реализации :

  • x.compareTo(e2) должны иметь такое же значение boolean как x.equals (y)

Это гарантирует, что мы можем безопасно использовать объекты в отсортированных наборах и отсортированных картах.

2.5. Последовательность с равными

Давайте посмотрим, что может произойти, когда сравнить Для и равняется реализации не являются последовательными.

На нашем примере сравнить Для метод проверки забитых мячей, в то время как равняется метод проверки имени игрока:

@Override
public int compareTo(FootballPlayer anotherPlayer) {
    return this.goalsScored - anotherPlayer.goalsScored;
}

@Override
public boolean equals(Object object) {
    if (this == object)
        return true;
    if (object == null || getClass() != object.getClass())
        return false;
    FootballPlayer player = (FootballPlayer) object;
    return name.equals(player.name);
}

Это может привести к неожиданному поведению при использовании этого класса в отсортированных наборах или отсортированных картах:

FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 800);

TreeSet set = new TreeSet<>();
set.add(messi);
set.add(ronaldo);

assertThat(set).hasSize(1);
assertThat(set).doesNotContain(ronaldo);

Отсортированный набор выполняет все сравнения элементов с помощью сравнить Для а не равняется метод. Таким образом, два игрока кажутся эквивалентными с его точки зрения, и он не добавит второго игрока.

3. Сортировка коллекций

Основная цель Сопоставимые интерфейс должен позволяют естественную сортировку элементов, сгруппированных в коллекции или массивы .

Мы можем сортировать все объекты, которые реализуют Сопоставимые использование методов java утилиты Collections.sort или Arrays.sort .

3.1. Основные классы Java

Большинство основных классов Java, таких как Струнные , Интегер , или Двойное , уже реализуют Сопоставимые интерфейс.

Таким образом, сортировка их очень проста, так как мы можем повторно использовать их существующую, естественную реализацию сортировки.

Сортировка чисел в их естественном порядке приведет к восходящему порядку:

int[] numbers = new int[] {5, 3, 9, 11, 1, 7};
Arrays.sort(numbers);
assertThat(numbers).containsExactly(1, 3, 5, 7, 9, 11);

С другой стороны, естественная сортировка строк приведет к алфавитному порядку:

String[] players = new String[] {"ronaldo",  "modric", "ramos", "messi"};
Arrays.sort(players);
assertThat(players).containsExactly("messi", "modric", "ramos", "ronaldo");

3.2. Пользовательские классы

В отличие от этого, для любого пользовательские классы, чтобы быть сортируемыми, мы должны вручную реализовать Сопоставимые интерфейс .

Компилятор Java будет бросать ошибку, если мы попытаемся сортировать коллекцию объектов, которые не реализуют Сопоставимые .

Если мы попробуем то же самое с массивами, это не потерпит неудачу во время компиляции. Тем не менее, это приведет к исключению из класса литого времени выполнения:

HandballPlayer duvnjak = new HandballPlayer("Duvnjak", 197);
HandballPlayer hansen = new HandballPlayer("Hansen", 196);
HandballPlayer[] players = new HandballPlayer[] {duvnjak, hansen};
assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> Arrays.sort(players));

3.3. TreeMap и TreeSet

TreeMap и TreeSet являются двумя реализациями из рамочной программы коллекций Java, которые помочь нам с автоматической сортировкой их элементов .

Мы можем использовать объекты, которые реализуют Сопоставимые интерфейс на отсортированной карте или в качестве элементов в отсортированного набора.

Рассмотрим пример пользовательского класса, который сравнивает игроков на основе количества забитых ими голов:

@Override
public int compareTo(FootballPlayer anotherPlayer) {
    return Integer.compare(this.goalsScored, anotherPlayer.goalsScored);
}

В нашем примере ключи автоматически сортируются на основе критериев, определенных в сравнить Для реализация:

FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 900);
FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer modric = new FootballPlayer("modric", 100);

Map players = new TreeMap<>();
players.put(ronaldo, "forward");
players.put(messi, "forward");
players.put(modric, "midfielder");

assertThat(players.keySet()).containsExactly(modric, messi, ronaldo);

4. Альтернатива компаратора

Помимо натуральной сортировки, Java также позволяет гибко определять конкретную логику заказа.

Интерфейс Компаратора позволяет проводить несколько различных стратегий сравнения, отделенных от объектов, которые мы сортим:

FootballPlayer ronaldo = new FootballPlayer("Ronaldo", 900);
FootballPlayer messi = new FootballPlayer("Messi", 800);
FootballPlayer modric = new FootballPlayer("Modric", 100);

List players = Arrays.asList(ronaldo, messi, modric);
Comparator nameComparator = Comparator.comparing(FootballPlayer::getName);
Collections.sort(players, nameComparator);

assertThat(players).containsExactly(messi, modric, ronaldo);

Как правило, это также хороший выбор, когда мы не хотим или не можем изменить исходный код объектов, которые мы хотим сортировать.

5. Заключение

В этой статье мы рассмотрели, как мы можем использовать Сопоставимые интерфейс для определения естественного алгоритма сортировки для наших классов Java. Мы рассмотрели общую сломанную схему и определили, как правильно реализовать сравнить Для метод.

Мы также изучили коллекции сортировки, которые содержат как основные, так и пользовательские классы. Далее мы рассмотрели вопрос о реализации сравнить Для метод в классах, используемых в отсортированных наборах и отсортированных картах.

Наконец, мы рассмотрели несколько случаев использования, когда мы должны использовать Компаратор интерфейс вместо этого.

Как всегда, исходный код доступен более на GitHub .