CodeGym /Corsi /JAVA 25 SELF /Interfaccia Comparator: creazione, utilizzo

Interfaccia Comparator: creazione, utilizzo

JAVA 25 SELF
Livello 29, Lezione 3
Disponibile

1. Introduzione

Nella vita è raro che un unico modo di confrontare gli oggetti sia sufficiente. Immaginate di avere un elenco di utenti: a volte volete ordinarli per nome, a volte — per età, e a volte — per lunghezza del cognome. Oppure avete una classe di cui non siete voi gli autori e non potete aggiungervi compareTo. Proprio per questi casi in Java esiste l’interfaccia Comparator.

Quando Comparable non basta

  • La classe non si può modificare (ad esempio, proviene da una libreria di terze parti).
  • Servono più criteri di ordinamento (per campi diversi).
  • Volete separare la logica di confronto dalla classe stessa (ad esempio, ordinare in modo diverso in parti diverse del programma).

Analogia
Se Comparable è l’«ordine naturale» integrato dell’oggetto, allora Comparator è un arbitro esterno che può valutare i vostri oggetti secondo qualsiasi criterio: oggi per nome, domani — per età, dopodomani — per lunghezza del nome.

2. Interfaccia Comparator: sintassi e contratto

Dichiarazione dell’interfaccia

public interface Comparator<T> {
    int compare(T o1, T o2);
}

Il metodo compare deve restituire:

  • Un numero negativo se il primo oggetto è «minore» del secondo.
  • 0 se sono uguali.
  • Un numero positivo se il primo è «maggiore» del secondo.

Il contratto è lo stesso di Comparable, solo che ora si confrontano due oggetti e non l’«oggetto corrente» e «l’altro» tramite compareTo.

Esempio: un Comparator per ordinare per cognome

Supponiamo di avere una classe Person:

public class Person {
    private String firstName;
    private String lastName;
    private int age;

    // Costruttore e getter
    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
}

Creiamo un comparator che ordina per cognome:

import java.util.Comparator;

public class LastNameComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return a.getLastName().compareTo(b.getLastName());
    }
}

Nota: il metodo compareTo delle stringhe (String) le confronta in ordine alfabetico.

3. Uso di Comparator: ordinamento delle collezioni

Ordinare con un Comparator

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Anna", "Kostetskaya", 25));
        people.add(new Person("Boris", "Novak", 20));
        people.add(new Person("Viktoriya", "Bell", 22));

        // Ordinamento per cognome
        Collections.sort(people, new LastNameComparator());

        for (Person p : people) {
            System.out.println(p.getLastName() + " " + p.getFirstName());
        }
    }
}

Risultato:

Novak Boris
Kostetskaya Anna
Bell Viktoriya

Ordinamento per età con un Comparator

Anche se la classe implementa già Comparable per il nome, si può creare un comparator separato — per l’età:

public class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return Integer.compare(a.getAge(), b.getAge());
    }
}

E usarlo allo stesso modo:

Collections.sort(people, new AgeComparator());

Risultato:

Boris Novak (20)
Viktoriya Bell (22)
Anna Kostetskaya (25)

Esempio: scelta del Comparator «al volo»

Collections.sort(people, new LastNameComparator()); // Per cognome
Collections.sort(people, new AgeComparator());      // Per età

4. Classi anonime e lambda

I comparator si possono creare «al volo», senza dichiarare classi separate.

Classe anonima

Collections.sort(people, new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return a.getFirstName().compareTo(b.getFirstName());
    }
});

Espressione lambda

Collections.sort(people, (a, b) -> a.getFirstName().compareTo(b.getFirstName()));

Oppure ancora più breve con il metodo della lista List.sort:

people.sort((a, b) -> a.getFirstName().compareTo(b.getFirstName()));
  • Le classi anonime sono il modo vecchio, verboso.
  • Le lambda sono moderne e compatte.

5. Esempi: ordinamento per criteri diversi

Ordinamento per lunghezza del cognome

Comparator<Person> byLastNameLength = (a, b) ->
        Integer.compare(a.getLastName().length(), b.getLastName().length());
people.sort(byLastNameLength);

Ordinamento per età, poi per nome (multi-livello)

Comparator<Person> byAgeThenName = (a, b) -> {
    int cmp = Integer.compare(a.getAge(), b.getAge());
    if (cmp != 0) return cmp;
    return a.getFirstName().compareTo(b.getFirstName());
};
people.sort(byAgeThenName);

Uso di un Comparator per la ricerca (esempio)

Il comparator è utile non solo per l’ordinamento, ma anche per la ricerca in collezioni ordinate:

// people deve essere ordinato per età!
Person key = new Person("?", "?", 22);
int idx = Collections.binarySearch(people, key, new AgeComparator());
if (idx >= 0) {
    System.out.println("Trovata persona con età 22: " + people.get(idx));
}

6. Best practice e particolarità di lavoro con Comparator

Non infrangete il contratto

  • Se compare(a, b) restituisce 0, allora anche compare(b, a) deve restituire 0.
  • Se compare(a, b) > 0, allora compare(b, a) < 0.
  • Tenete conto dei possibili valori null (vedi sotto).

Non dimenticate equals e hashCode

Sebbene i comparator confrontino gli oggetti «a modo loro», per strutture come TreeSet o quando si cercano chiavi in TreeMap è importante che la logica di confronto del comparator sia coerente con equals. Altrimenti si possono ottenere risultati inaspettati: due oggetti diversi sono considerati uguali dal comparator ma non uguali secondo equals.

Ordinamento con gestione di null

Se i campi possono essere null, usate gli helper «pronti»:

Comparator<Person> byLastNameNullSafe = Comparator.comparing(
    Person::getLastName,
    Comparator.nullsLast(String::compareTo)
);
people.sort(byLastNameNullSafe);

7. Dettagli utili

Tabella: confronto tra Comparable e Comparator

Comparable Comparator
Dove si implementa? Nella classe stessa In una classe/lambda separata
Metodo
int compareTo(T o)
int compare(T o1, T o2)
Quante varianti? Solo uno «naturale» Quante se ne vogliono, per ogni esigenza
Utilizzo
Collections.sort(list)
Collections.sort(list, comp)
Si può per classi di terzi? No Sì

Esempio: ordinamento decrescente

Si può invertire l’ordine manualmente:

Comparator<Person> byAgeDesc = (a, b) -> Integer.compare(b.getAge(), a.getAge());
people.sort(byAgeDesc);

Oppure con reversed():

Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);
people.sort(byAge.reversed());

8. Errori tipici nell’uso di Comparator

Errore n. 1: violazione del contratto di confronto. Se dimenticate che compare(a, b) e compare(b, a) devono avere segni opposti, o restituite valori arbitrari (ad esempio, semplicemente la differenza — a.getAge() - b.getAge(), che può andare in overflow), il risultato sarà imprevedibile. Usate Integer.compare e non la sottrazione: è più sicuro.

Errore n. 2: ignorare i valori null. Se i campi in base ai quali confrontate possono essere null, trattate obbligatoriamente questo caso (ad esempio con Comparator.nullsFirst/Comparator.nullsLast), altrimenti è facile ottenere una NullPointerException nel momento meno opportuno.

Errore n. 3: criteri di ordinamento instabili. Se il comparator restituisce valori diversi per gli stessi oggetti (ad esempio usa un numero casuale o un campo altamente mutevole), l’ordinamento può comportarsi in modo caotico.

Errore n. 4: incoerenza con equals. Se compare(a, b) == 0, ma a.equals(b) è false, collezioni come TreeSet e TreeMap potrebbero non funzionare come vi aspettate. È preferibile che l’uguaglianza secondo il comparator e secondo equals coincidano.

Errore n. 5: ordinare senza Comparator per classi di terzi. Se provate a ordinare oggetti di una «classe altrui» senza Comparable e senza fornire un Comparator, otterrete un errore di compilazione. Passate un comparator esplicito.

1
Compito
JAVA 25 SELF, livello 29, lezione 3
Bloccato
Ordinamento della lista dei partecipanti per età all'evento 🧑‍🎓
Ordinamento della lista dei partecipanti per età all'evento 🧑‍🎓
1
Compito
JAVA 25 SELF, livello 29, lezione 3
Bloccato
Ordinamento dei partecipanti per lunghezza del nome per i badge 🏷️
Ordinamento dei partecipanti per lunghezza del nome per i badge 🏷️
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION