CodeGym /Corsi /JAVA 25 SELF /Interfaccia Comparable: implementazione, compareTo

Interfaccia Comparable: implementazione, compareTo

JAVA 25 SELF
Livello 29, Lezione 2
Disponibile

1. Il problema del confronto tra oggetti

Promemoria: confronto dei riferimenti e confronto degli oggetti

Sapete già che in Java l’operatore ==, quando lavora con gli oggetti, confronta i loro riferimenti — cioè se si trovano allo stesso indirizzo di memoria. Due oggetti con gli stessi campi, ma creati con new, saranno diversi per ==.

Person p1 = new Person("Sasha", 20);
Person p2 = new Person("Sasha", 20);

System.out.println(p1 == p2); // false — sono oggetti diversi in memoria!

E se vogliamo sapere se sono uguali per contenuto, usiamo equals(), hashCode() ... beh, lo sapete già. E se invece dobbiamo capire chi è «più grande», «più piccolo», «più in alto in ordine alfabetico»? Per esempio, per ordinare un elenco di utenti per età o per nome.

La necessità di ordinare e cercare oggetti

Supponiamo di avere un elenco di utenti e di volerli ordinare per età:

List<Person> people = new ArrayList<>();
people.add(new Person("Vasya", 25));
people.add(new Person("Petya", 20));
people.add(new Person("Katya", 30));

// Come ordinare?
Collections.sort(people); // Ops! E Java non sa come confrontare Person!

Il compilatore si lamenterà subito: la classe Person non implementa l’interfaccia Comparable. Java non legge nel pensiero e non sa che cosa significhi per noi «maggiore» o «minore» per Person. Per insegnarglielo, dobbiamo descrivere esplicitamente le regole di confronto.

2. L’interfaccia Comparable

Dichiarazione dell’interfaccia

L’interfaccia Comparable è il modo standard per dire a Java: «La mia classe è confrontabile, ed ecco come farlo».

public interface Comparable<T> {
    int compareTo(T o);
}

Il vecchio amico a.compareTo(b) restituirà:

  • un numero negativo — significa che a è «minore» di b.
  • Se 0 — gli oggetti sono considerati uguali.
  • un numero positivo — a è «maggiore» di b.

Esempio: implementazione di compareTo per la classe Person

Creiamo una classe Person confrontabile per età:

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter (per gli esempi seguenti)
    public String getName() { return name; }
    public int getAge() { return age; }

    // Implementazione del metodo compareTo
    @Override
    public int compareTo(Person other) {
        // Ordinamento per età (crescente)
        return Integer.compare(this.age, other.age);
        // Alternativa: return this.age - other.age;
    }
}

Punto importante: se volete ordinare in ordine decrescente, basta invertire gli argomenti: Integer.compare(other.age, this.age).

Analogia. compareTo è come un giudice in una gara: deve decidere chiaramente chi è davanti, chi è dietro e chi è allo stesso livello. Se tutti i giudici (i metodi compareTo) giudicano in modo diverso — inizierà il caos!

3. Uso di Comparable

Ordinare le collezioni con Comparable

Ora che la nostra classe implementa Comparable, l’ordinamento funziona «out of the box»:

List<Person> people = new ArrayList<>();
people.add(new Person("Vasya", 25));
people.add(new Person("Petya", 20));
people.add(new Person("Katya", 30));

Collections.sort(people); // Usa compareTo!

for (Person p : people) {
    System.out.println(p.getName() + " (" + p.getAge() + ")");
}
// Petya (20)
// Vasya (25)
// Katya (30)

In modo analogo funziona anche il metodo sort della lista:

people.sort(null); // Se si passa null, viene usato compareTo

Ordinamento per nome

Se vogliamo ordinare per nome — cambiamo l’implementazione:

@Override
public int compareTo(Person other) {
    return this.name.compareTo(other.name);
}

Ordinamento per più campi

A volte bisogna confrontare prima per un campo e, in caso di parità, per un altro:

@Override
public int compareTo(Person other) {
    int cmp = Integer.compare(this.age, other.age);
    if (cmp != 0) return cmp;
    return this.name.compareTo(other.name);
}

4. Best practice nell’implementazione di Comparable

Rispettate il contratto di Comparable

  • Se a.compareTo(b) == 0, allora b.compareTo(a) deve essere necessariamente 0.
  • Se a.compareTo(b) < 0, allora b.compareTo(a) deve essere > 0 (e viceversa).
  • Se a.compareTo(b) == 0, è auspicabile che a.equals(b) sia true (ma non è strettamente obbligatorio).

Perché è importante?
Le collezioni (per esempio, TreeSet, TreeMap) e i metodi di ordinamento possono comportarsi in modo imprevedibile se il contratto è violato. Per esempio, possono comparire «duplicati» in una collezione in cui non dovrebbero esserci.

Non dimenticate equals e hashCode

Se implementate compareTo, chiedetevi: sono implementati correttamente anche equals e hashCode? Soprattutto se la vostra classe verrà usata in collezioni come HashSet o Map.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Person)) return false;
    Person other = (Person) o;
    return age == other.age && Objects.equals(name, other.name);
}

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

Non usate in compareTo campi che possono essere null senza controlli!

Se un campo può essere null, usate confronti sicuri:

@Override
public int compareTo(Person other) {
    return Objects.compare(this.name, other.name, Comparator.nullsFirst(String::compareTo));
}

Non modificate i campi coinvolti in compareTo se l’oggetto è già in una collezione ordinata

Questo può far «perdere» l’oggetto all’interno della collezione — per esempio, in TreeSet o TreeMap.

5. Sviluppiamo l’app didattica: ordinare gli utenti

Passo 1: definiamo la classe

public class Person implements Comparable<Person> {
    private String name;
    private int age;

    // ... costruttore, getter, compareTo, equals, hashCode ...
}

Passo 2: aggiungiamo gli utenti

List<Person> people = new ArrayList<>();
people.add(new Person("Masha", 23));
people.add(new Person("Grisha", 19));
people.add(new Person("Anya", 25));

Passo 3: ordiniamo e stampiamo

Collections.sort(people);

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

Risultato:

Grisha (19)
Masha (23)
Anya (25)

6. Schema di funzionamento di Comparable

┌────────────────────────────┐
│   La vostra classe (Person)│
├────────────────────────────┤
│ implements Comparable      │
│   ↓                        │
│ public int compareTo(T o)  │
│   ↓                        │
│ (this < o) → -1            │
│ (this == o) → 0            │
│ (this > o) → 1             │
└────────────────────────────┘
        │
        ▼
Collections.sort(list)
        │
        ▼
   L'ordinamento funziona!

7. Dettagli utili

Come funziona Collections.sort

  • Se la lista contiene oggetti che implementano Comparable, l’ordinamento userà il loro metodo compareTo.
  • Se non lo implementano — si avrà un errore di compilazione.
  • Per i tipi standard (Integer, String e altri) Comparable è già implementato.

Si possono avere più modi di confronto?

  • In una classe — un solo «ordinamento naturale» tramite Comparable.
  • Per ordini alternativi usate Comparator (lezione successiva).

Esempio: compareTo per le stringhe

String a = "apple";
String b = "banana";
System.out.println(a.compareTo(b)); // numero negativo, perché "apple" < "banana"

Tabella: cosa restituisce compareTo

Confronto Valore restituito
this < o
< 0
this == o
0
this > o
> 0

8. Errori tipici nell’implementazione di Comparable

Errore n. 1: violazione del contratto di compareTo.
Se a.compareTo(b) restituisce 0, ma b.compareTo(a) non è 0, le collezioni si comporteranno in modo strano. Per esempio, TreeSet può considerare gli oggetti diversi e aggiungerli entrambi.

Errore n. 2: utilizzo di campi non inizializzati (null).
Se il campo con cui confrontate può essere null e non fate il controllo — otterrete una NullPointerException.

Errore n. 3: incoerenza tra compareTo ed equals.
Se compareTo dice che gli oggetti sono uguali (0), ma equals dice che sono diversi (false), questo porterà a bug quando si lavora con le collezioni.

Errore n. 4: modifica dei campi coinvolti in compareTo dopo l’aggiunta a una collezione ordinata.
È come cambiare il cognome sul passaporto mentre siete già in una fila in ordine alfabetico. La collezione può «perdere» il vostro oggetto.

Errore n. 5: restituire solo -1, 0 o 1.
Il metodo compareTo può restituire qualsiasi numero negativo o positivo, non necessariamente proprio -1 o 1. Ma per semplicità si usano spesso -1/0/1.

1
Compito
JAVA 25 SELF, livello 29, lezione 2
Bloccato
Ordinamento del catalogo di libri con più criteri 📖
Ordinamento del catalogo di libri con più criteri 📖
1
Compito
JAVA 25 SELF, livello 29, lezione 2
Bloccato
Gestione dell'unicità e ordinamento delle città 🏙️
Gestione dell'unicità e ordinamento delle città 🏙️
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION