Comparatore e comparabile in Java

1. Introduzione

I confronti in Java sono abbastanza facili, finché non lo sono.

Quando si lavora con tipi personalizzati o si cerca di confrontare oggetti che non sono direttamente confrontabili, è necessario utilizzare una strategia di confronto. Possiamo costruirne uno semplicemente, ma utilizzando le interfacce Comparator o Comparable .

2. Impostazione dell'esempio

Facciamo un esempio di una squadra di calcio, dove vogliamo allineare i giocatori in base alla loro classifica.

Inizieremo creando una semplice classe Player :

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

Successivamente, creiamo una classe PlayerSorter per creare la nostra raccolta e proviamo a ordinarla utilizzando 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); } 

Qui, come previsto, questo si traduce in un errore in fase di compilazione:

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

Capiamo cosa abbiamo sbagliato qui.

3. Comparabile

Come suggerisce il nome, Comparable è un'interfaccia che definisce una strategia di confronto di un oggetto con altri oggetti dello stesso tipo. Questo è chiamato "ordinamento naturale" della classe.

Di conseguenza, per poter ordinare, dobbiamo definire il nostro oggetto Player come comparabile implementando l' interfaccia Comparable :

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

L'ordinamento è deciso dal valore restituito dal metodo compareTo () . L'Integer.compare (x, y) restituisce -1 se x è minore di y , restituisce 0 se sono uguali, e ritorna 1 altrimenti.

Il metodo restituisce un numero che indica se l'oggetto confrontato è minore, uguale o maggiore dell'oggetto passato come argomento.

Infine, quando eseguiamo il nostro PlayerSorter ora, possiamo vedere i nostri giocatori ordinati in base alla loro classifica:

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

Ora che abbiamo una chiara comprensione dell'ordinamento naturale con Comparable , vediamo come possiamo usare altri tipi di ordinamento, in modo più flessibile rispetto all'implementazione diretta di un'interfaccia.

4. Comparatore

L' interfaccia Comparator definisce un metodo compare (arg1, arg2) con due argomenti che rappresentano oggetti confrontati e funziona in modo simile al metodo Comparable.compareTo () .

4.1. Creazione di comparatori

Per creare un comparatore, dobbiamo implementare l' interfaccia del comparatore .

Nel nostro primo esempio, creeremo un comparatore per utilizzare l' attributo ranking di Player per ordinare i giocatori:

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

Allo stesso modo, possiamo creare un comparatore per utilizzare l' attributo età del giocatore per ordinare i giocatori:

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

4.2. Comparatori in azione

Per dimostrare il concetto, modifichiamo il nostro PlayerSorter introducendo un secondo argomento al metodo Collections.sort che in realtà è l'istanza di Comparator che vogliamo utilizzare.

Usando questo approccio, possiamo sovrascrivere l'ordine naturale :

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

Ora, eseguiamo il nostro PlayerRankingSorter per vedere il risultato:

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

Se vogliamo un diverso ordinamento, dobbiamo solo cambiare il Comparatore che stiamo usando:

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

Ora, quando eseguiamo il nostro PlayerAgeSorter , possiamo vedere un diverso ordinamento per età:

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

4.3. Comparatori Java 8

Java 8 fornisce nuovi modi per definire i comparatori utilizzando espressioni lambda e il metodo factory statico comparing () .

Vediamo un rapido esempio di come utilizzare un'espressione lambda per creare un comparatore :

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

Il metodo Comparator.comparing accetta un metodo che calcola la proprietà che verrà utilizzata per confrontare gli elementi e restituisce un'istanza di Comparator corrispondente :

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

Puoi esplorare le funzionalità di Java 8 in modo approfondito nella nostra guida alla comparazione di Java 8 Comparator.com.

5. Comparatore vs comparabile

L' interfaccia Comparable è una buona scelta quando viene utilizzata per definire l'ordinamento predefinito o, in altre parole, se è il modo principale per confrontare gli oggetti.

Quindi, dobbiamo chiederci perché usare un Comparatore se abbiamo già Comparable ?

Ci sono diversi motivi per cui:

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

E, come al solito, il codice sorgente può essere trovato su GitHub.