Comparator și comparabil în Java

1. Introducere

Comparațiile în Java sunt destul de ușoare - până când nu sunt.

Când lucrăm cu tipuri personalizate sau când încercăm să comparăm obiecte care nu sunt direct comparabile, trebuie să folosim o strategie de comparație. Putem construi unul simplu, dar folosind comparatorul sau interfețele comparabile .

2. Configurarea exemplului

Să luăm un exemplu de echipă de fotbal - în care vrem să aliniem jucătorii după clasamentul lor.

Vom începe prin a crea o clasă de jucător simplă :

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

Apoi, să creăm o clasă PlayerSorter pentru a crea colecția noastră și să încercăm să o sortăm folosind 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); } 

Aici, așa cum era de așteptat, rezultă o eroare în timpul compilării:

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

Să înțelegem ce am greșit aici.

3. Comparabil

După cum sugerează și numele, Comparable este o interfață care definește o strategie de comparare a unui obiect cu alte obiecte de același tip. Aceasta se numește „ordonarea naturală” a clasei.

În consecință, pentru a putea sorta - trebuie să ne definim obiectul Player ca fiind comparabil prin implementarea interfeței comparabile :

public class Player implements Comparable { // same as before @Override public int compareTo(Player otherPlayer) { return Integer.compare(getRanking(), otherPlayer.getRanking()); } } 

Ordinea de sortare este decisă de valoarea returnată a metodei compareTo () . Integer.compare (x, y) returnează -1 dacă x este mai mic decât y , returnează 0 dacă ele sunt egale, și se întoarce 1 altfel.

Metoda returnează un număr care indică dacă obiectul comparat este mai mic decât, egal cu sau mai mare decât obiectul care este trecut ca argument.

În cele din urmă, atunci când rulăm PlayerSorter acum, putem vedea jucătorii noștri sortați după clasamentul lor:

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

Acum, că avem o înțelegere clară a ordonării naturale cu Comparable , să vedem cum putem folosi alte tipuri de ordonare, într-un mod mai flexibil decât implementarea directă a unei interfețe.

4. Comparator

Comparator Interfața definește o (, arg2 arg1) compară metoda cu două argumente care reprezintă comparate obiecte și funcționează în mod similar cu Comparable.compareTo () metoda.

4.1. Crearea comparatoarelor

Pentru a crea un Comparator, trebuie să implementăm interfața Comparator .

În primul nostru exemplu, vom crea un comparator pentru a utiliza atributul de clasare al jucătorului pentru a sorta jucătorii:

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

În mod similar, putem crea un comparator pentru a utiliza atributul de vârstă al jucătorului pentru a sorta jucătorii:

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

4.2. Comparatori în acțiune

Pentru a demonstra conceptul, să modificăm PlayerSorter-ul nostru introducând un al doilea argument în metoda Collections.sort, care este de fapt instanța Comparator pe care dorim să o folosim.

Folosind această abordare, putem anula ordinea naturală :

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

Acum, să rulăm PlayerRankingSorter pentru a vedea rezultatul:

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

Dacă dorim o altă ordine de sortare, trebuie doar să schimbăm comparatorul pe care îl folosim:

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

Acum, când rulăm PlayerAgeSorter , putem vedea o altă ordine de sortare în funcție de vârstă:

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

4.3. Comparatoare Java 8

Java 8 oferă noi modalități de definire a comparatorilor utilizând expresii lambda și metoda comparing () statică din fabrică.

Să vedem un exemplu rapid de cum să utilizați o expresie lambda pentru a crea un comparator :

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

Metoda Comparator.comparing ia o metodă de calculare a proprietății care va fi utilizată pentru compararea articolelor și returnează o instanță de comparare potrivită :

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

Puteți explora în detaliu funcționalitatea Java 8 în ghidul nostru de comparare Java 8 Comparator.com.

5. Comparator vs Comparabil

Comparabil Interfața este o alegere bună atunci când este utilizat pentru a defini ordinea prestabilită sau, cu alte cuvinte, în cazul în care este principala cale de a compara obiecte.

Apoi, trebuie să ne întrebăm de ce să folosim un comparator dacă avem deja comparabil ?

Există mai multe motive pentru care:

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

Și, ca de obicei, codul sursă poate fi găsit pe GitHub.