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 като сравним чрез прилагане на сравнимия интерфейс:
public class Player implements Comparable { // same as before @Override public int compareTo(Player otherPlayer) { return Integer.compare(getRanking(), otherPlayer.getRanking()); } }
Редът за сортиране се определя от връщаната стойност на метода compareTo () . В Integer.compare (х, у) се връща -1 ако х е по-малко от Y , връща 0, ако са еднакви, и се връща 1 друго.
Методът връща число, указващо дали обектът, който се сравнява, е по-малък от, равен на или по-голям от обекта, който се предава като аргумент.
И накрая, когато стартираме нашия PlayerSorter сега, можем да видим нашите играчи, сортирани по класиране:
Before Sorting : [John, Roger, Steven] After Sorting : [Steven, John, Roger]
Сега, когато имаме ясно разбиране за естественото подреждане с Comparable , нека видим как можем да използваме други видове подреждане, по по-гъвкав начин, отколкото директно прилагане на интерфейс.
4. Компаратор
В сравняване интерфейс дефинира сравнение (, arg2 arg1) метод с два аргумента, които представляват в сравнение обекти и работи подобно на 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 предоставя нови начини за дефиниране на сравнители чрез използване на ламбда изрази и метода за сравнение () static factory.
Нека да видим бърз пример за това как да използваме ламбда израз за създаване на сравнител :
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.