Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Comparació d’objectes

Tenim una llista de persones i la volem ordenada:

List<Persona> persones = new ArrayList<>();
Collections.sort(persones);      // error de compilació

Java no pot endevinar què vol dir que una persona sigui “menor” que una altra. Li ho hem de dir, i hi ha dues maneres de fer-ho:

  • Fer la classe comparable, quan té un ordre propi i evident.
  • Escriure un comparador a part, quan l’ordre és un entre molts de possibles o quan no podem tocar la classe.

Comparable: l’ordre natural

Una classe és comparable si implementa la interfície Comparable<T>, que té un únic mètode:

int compareTo(T o)

No retorna un booleà, sinó un enter del qual només importa el signe:

RetornSignificat
NegatiuAquest objecte va abans que o
ZeroEls dos van a la mateixa posició
PositiuAquest objecte va després que o

Amb això, la classe ja té un ordre natural:

public class Persona implements Comparable<Persona> {
    private final String cognom;
    private final int edat;

    @Override
    public int compareTo(Persona altra) {
        return this.cognom.compareTo(altra.cognom);
    }
}

Ara Collections.sort(persones) compila i ordena per cognom.

Moltes classes de la biblioteca estàndard ja són comparables: String per ordre alfabètic, les classes embolcall dels tipus primitius (Integer, Double, Character) per ordre numèric, i LocalDate o LocalDateTime per ordre cronològic. Per això una List<String> es pot ordenar sense fer res més.

Dos detalls del contracte

No implementis compareTo() amb una resta. És el costum heretat de C i pot desbordar amb valors extrems, retornant el signe equivocat:

return this.edat - altra.edat;                    // pot desbordar
return Integer.compare(this.edat, altra.edat);    // correcte

L’ordre natural hauria de ser coherent amb equals(). No és obligatori, però si no ho és les col·leccions ordenades es comporten de manera sorprenent: un TreeSet i un TreeMap decideixen si dos elements són el mateix amb compareTo(), no amb equals().

Set<Persona> conjunt = new TreeSet<>();
conjunt.add(new Persona("Puig", 30));
conjunt.add(new Persona("Puig", 45));   // no s'afegeix: compareTo() retorna 0
conjunt.size();                          // 1

Amb un HashSet hi hauria les dues persones, perquè allà mana equals(). Si l’ordre natural només mira el cognom, val més que equals() també, o que la comparació desempati amb algun camp més.

Comparator: ordres alternatius

Un Comparator<T> és un objecte a part que sap comparar dos elements. També té un únic mètode, amb el mateix conveni de signes:

int compare(T o1, T o2)

La manera pràctica d’escriure’n un no és implementar la interfície, sinó fer servir els constructors de Comparator, que reben el camp pel qual vols ordenar:

persones.sort(Comparator.comparing(Persona::getCognom));
persones.sort(Comparator.comparingInt(Persona::getEdat));

Es poden encadenar per desempatar i invertir:

persones.sort(Comparator.comparing(Persona::getCognom)
                        .thenComparing(Persona::getNom)
                        .reversed());

Fes servir comparingInt(), comparingLong() i comparingDouble() quan el camp és primitiu: eviten l’autoboxing de cada comparació.

Si algun camp pot ser null, embolcalla el comparador amb Comparator.nullsFirst() o nullsLast(), o rebràs un NullPointerException enmig de l’ordenació.

Quin dels dos

SituacióTria
La classe té un ordre evident i únic (un import, una data)Comparable
Hi ha diversos criteris raonables (per cognom, per edat, per data d’alta)Comparator
La classe no és teva i no la pots modificarComparator
Vols l’ordre per defecte, però en un cas concret te’n cal un altreTots dos: ordre natural i un comparador per a l’excepció

En general val més fer la classe comparable si l’ordre forma part del que significa la classe. Un comparador és millor quan l’ordre depèn de qui la fa servir i no de la classe mateixa.

On es fa servir l’ordre

Un cop tens l’ordre definit, el fan servir moltes parts de la biblioteca:

  • list.sort(cmp) i Collections.sort(list), entre altres algorismes.
  • Les col·leccions ordenades TreeSet i TreeMap, i també PriorityQueue. Totes tenen un constructor que accepta un Comparator, i sense ell fan servir l’ordre natural.
  • Les operacions sorted(), max() i min() de l’Stream API.

Referències

Last change: , commit: 87dfa51