Автор оригинала: 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); TreeSetset = 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); Mapplayers = 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); Listplayers = Arrays.asList(ronaldo, messi, modric); Comparator nameComparator = Comparator.comparing(FootballPlayer::getName); Collections.sort(players, nameComparator); assertThat(players).containsExactly(messi, modric, ronaldo);
Как правило, это также хороший выбор, когда мы не хотим или не можем изменить исходный код объектов, которые мы хотим сортировать.
5. Заключение
В этой статье мы рассмотрели, как мы можем использовать Сопоставимые интерфейс для определения естественного алгоритма сортировки для наших классов Java. Мы рассмотрели общую сломанную схему и определили, как правильно реализовать сравнить Для метод.
Мы также изучили коллекции сортировки, которые содержат как основные, так и пользовательские классы. Далее мы рассмотрели вопрос о реализации сравнить Для метод в классах, используемых в отсортированных наборах и отсортированных картах.
Наконец, мы рассмотрели несколько случаев использования, когда мы должны использовать Компаратор интерфейс вместо этого.
Как всегда, исходный код доступен более на GitHub .