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

Компаратор и сопоставимый в Java

Краткое и практическое руководство по интерфейсам сравнения и компаратора.

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

1. введение

Сравнения в Java довольно просты – пока это не так.

При работе с пользовательскими типами или при попытке сравнить объекты, которые непосредственно не сопоставимы, нам необходимо использовать стратегию сравнения. Мы можем построить его просто, но с использованием интерфейсов Comparator или Comparable .

2. Настройка примера

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

Мы начнем с создания простого Игрок класс:

public class Player {
    private int ranking;
    private String name;
    private int age;
    
    // constructor, getters, setters  
}

Затем давайте создадим класс Player Sorter для создания вашей коллекции и попытаемся отсортировать ее с помощью Collections.сортировка :

public static void main(String[] args) {
    List footballTeam = new ArrayList<>();
    Player player1 = new Player(59, "John", 20);
    Player player2 = new Player(67, "Roger", 22);
    Player player3 = new Player(45, "Steven", 24);
    footballTeam.add(player1);
    footballTeam.add(player2);
    footballTeam.add(player3);

    System.out.println("Before Sorting : " + footballTeam);
    Collections.sort(footballTeam);
    System.out.println("After Sorting : " + footballTeam);
}

Здесь, как и ожидалось, это приводит к ошибке во время компиляции:

The method sort(List) in the type Collections 
  is not applicable for the arguments (ArrayList)

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

3. Сопоставимые

Как следует из названия, Comparable – это интерфейс, определяющий стратегию сравнения объекта с другими объектами того же типа. Это называется “естественным упорядочением”класса.

Соответственно, чтобы иметь возможность сортировать – мы должны определить наш объект Player как сопоставимый, реализовав интерфейс Comparable :

public class Player implements Comparable {

    // same as before

    @Override
    public int compareTo(Player otherPlayer) {
        return Integer.compare(getRanking(), otherPlayer.getRanking());
    }

}

Порядок сортировки определяется возвращаемым значением метода compareTo () . | Integer.compare(x, y) возвращает -1 , если x меньше y , возвращает 0, если они равны, и возвращает 1 в противном случае.

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

Наконец, когда мы запускаем наш Сортировщик игроков сейчас, мы можем видеть наших Игроков , отсортированных по их рейтингу:

Before Sorting : [John, Roger, Steven]
After Sorting : [Steven, John, Roger]

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

4. Компаратор

Интерфейс Comparator определяет сравнение(arg1, arg2) метод с двумя аргументами, которые представляют сравниваемые объекты и работают аналогично методу Comparable.compareTo () .

4.1. Создание Компараторов

Чтобы создать Компаратор, мы должны реализовать интерфейс Компаратор .

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

public class PlayerRankingComparator implements Comparator {

    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return Integer.compare(firstPlayer.getRanking(), secondPlayer.getRanking());
    }

}

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

public class PlayerAgeComparator implements Comparator {

    @Override
    public int compare(Player firstPlayer, Player secondPlayer) {
       return Integer.compare(firstPlayer.getAge(), secondPlayer.getAge());
    }

}

4.2. Компараторы в действии

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

Используя этот подход, мы можем переопределить естественный порядок :

PlayerRankingComparator playerComparator = new PlayerRankingComparator();
Collections.sort(footballTeam, playerComparator);

Теперь давайте запустим наш PlayerRankingSorter, чтобы увидеть результат:

Before Sorting : [John, Roger, Steven]
After Sorting by ranking : [Steven, John, Roger]

Если нам нужен другой порядок сортировки, нам нужно только изменить Компаратор , который мы используем:

PlayerAgeComparator playerComparator = new PlayerAgeComparator();
Collections.sort(footballTeam, playerComparator);

Теперь, когда мы запускаем наш Сортировщик возраста игроков , мы видим другой порядок сортировки по возрасту:

Before Sorting : [John, Roger, Steven]
After Sorting by age : [Roger, John, Steven]

4.3. Компараторы Java 8

Java 8 предоставляет новые способы определения Компараторов с помощью лямбда-выражений и сравнения() статического заводского метода.

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

Comparator byRanking = 
  (Player player1, Player player2) -> Integer.compare(player1.getRanking(), player2.getRanking());

Метод Comparator.comparing принимает метод, вычисляющий свойство, которое будет использоваться для сравнения элементов, и возвращает соответствующий экземпляр Comparator :

Comparator byRanking = Comparator
  .comparing(Player::getRanking);
Comparator byAge = Comparator
  .comparing(Player::getAge);

Вы можете подробно изучить функциональность Java 8 в нашем руководстве по сравнению Java 8 Comparator.

5. Компаратор против сопоставимого

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

Тогда мы должны спросить себя, зачем использовать Компаратор , если у нас уже есть Сопоставимый ?

Есть несколько причин, по которым:

  • Иногда мы не можем изменить исходный код класса, объекты которого мы хотим отсортировать, что делает невозможным использование Comparable
  • Использование Компараторов позволяет нам избежать добавления дополнительного кода в наши доменные классы
  • Мы можем определить несколько различных стратегий сравнения, что невозможно при использовании Comparable

6. Избегайте трюка с вычитанием

В ходе этого урока мы использовали метод Integer.compare() для сравнения двух целых чисел. Можно возразить, что вместо этого мы должны использовать этот умный однострочный:

Comparator comparator = (p1, p2) -> p1.getRanking() - p2.getRanking();

Хотя он намного более лаконичен по сравнению с другими решениями, он может стать жертвой переполнения целых чисел в Java :

Player player1 = new Player(59, "John", Integer.MAX_VALUE);
Player player2 = new Player(67, "Roger", -1);

List players = Arrays.asList(player1, player2);
players.sort(comparator);

Так как -1 намного меньше, чем целое число .MAX_VALUE , “Роджер” должен стоять перед “Джоном” в отсортированной коллекции. Однако из-за переполнения целого числа/| “Целое число.MAX_VALUE – (-1)” будет меньше нуля . Таким образом, на основе Компаратора/Сопоставимого контракта, Целое число.MAX_VALUE меньше -1, что, очевидно, неверно.

Следовательно, несмотря на то, что мы ожидали, “Джон” стоит перед “Роджером” в отсортированной коллекции:

assertEquals("John", players.get(0).getName());
assertEquals("Roger", players.get(1).getName());

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

В этом уроке мы изучили интерфейсы Comparable и Comparator и обсудили различия между ними.

Чтобы понять более сложные темы сортировки, ознакомьтесь с другими нашими статьями , такими как Java 8 Comparator, сравнение Java 8 с лямбдами .

И, как обычно, исходный код можно найти на GitHub .