Порівняльний і порівнянний в Java

1. Вступ

Порівняння в Java досить просте - поки це не так.

Працюючи із власними типами або намагаючись порівняти об’єкти, які безпосередньо не можна порівняти, нам потрібно скористатися стратегією порівняння. Ми можемо створити його просто, але використовуючи інтерфейси Comparator або Comparable .

2. Налаштування прикладу

Візьмемо приклад футбольної команди - де ми хочемо виставити гравців за їх рейтингом.

Ми почнемо зі створення простого класу Player :

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

Далі, давайте створимо клас PlayerSorter, щоб створити нашу колекцію та спробуємо відсортувати її за допомогою Collections.sort :

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 (х, у) повертає -1 , якщо х менше у , повертає 0 , якщо вони рівні, і повертає 1 в іншому випадку.

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

Нарешті, запустивши наш PlayerSorter зараз, ми можемо побачити наших гравців, відсортованих за їх рейтингом:

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

Тепер, коли ми чітко розуміємо природне впорядкування з порівнянним , давайте подивимося, як ми можемо використовувати інші типи впорядкування, більш гнучко, ніж безпосередньо реалізуючи інтерфейс.

4. Порівняльник

Інтерфейс Comparator визначає метод порівняння (arg1, arg2) з двома аргументами, що представляють об'єкти, що порівнюються, і працює подібно до методу Comparable.compareTo () .

4.1. Створення компараторів

Щоб створити компаратор, ми повинні реалізувати інтерфейс компаратора .

У нашому першому прикладі ми створимо компаратор для використання атрибута ранжирування Player для сортування гравців:

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

Подібним чином, ми можемо створити компаратор, щоб використовувати віковий атрибут Player для сортування гравців:

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

4.2. Компаратори в дії

Щоб продемонструвати концепцію, давайте модифікуємо наш PlayerSorter , вводячи другий аргумент до методу Collections.sort, який насправді є екземпляром Comparator, який ми хочемо використовувати.

Використовуючи цей підхід, ми можемо замінити природне впорядкування :

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);

Тепер, коли ми запускаємо наш PlayerAgeSorter , ми можемо бачити інший порядок сортування за віком:

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.com.

5. Компаратор проти порівняльного

Порівнянні інтерфейс вибір хороший , коли використовується для визначення упорядкування за замовчуванням , або, іншими словами, якщо це основний спосіб порівняння об'єктів.

Тоді ми повинні запитати себе, навіщо використовувати компаратор, якщо ми вже маємо порівняльний ?

Є кілька причин, чому:

  • Sometimes, we can't modify the source code of the class whose objects we want to sort, thus making the use of Comparable impossible
  • Using Comparators allows us to avoid adding additional code to our domain classes
  • We can define multiple different comparison strategies which isn't possible when using Comparable

6. Avoiding the Subtraction Trick

Over the course of this tutorial, we used the Integer.compare() method to compare two integers. One might argue that we should use this clever one-liner instead:

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

Although it's much more concise compared to other solutions, it can be a victim of integer overflows in 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);

Since -1 is much less than the Integer.MAX_VALUE, “Roger” should come before the “John” in the sorted collection. However, due to integer overflow, the “Integer.MAX_VALUE – (-1)” will be less than zero. So, based on the Comparator/Comparable contract, the Integer.MAX_VALUE is less than -1, which is obviously incorrect.

Hence, despite what we expected, “John” comes before the “Roger” in the sorted collection:

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

7. Conclusion

In this tutorial, we explored the Comparable and Comparator interfaces and discussed the differences between them.

To understand more advanced topics of sorting, check out our other articles such as Java 8 Comparator, Java 8 Comparison with Lambdas.

І, як зазвичай, вихідний код можна знайти на GitHub.