CodeGym /Cursos /JAVA 25 SELF /Interfaz Comparable: implementación, compareTo

Interfaz Comparable: implementación, compareTo

JAVA 25 SELF
Nivel 29 , Lección 2
Disponible

1. El problema de comparar objetos

Recordatorio: comparar referencias y comparar objetos

Ya sabes que en Java el operador == al trabajar con objetos compara sus referencias — es decir, si están en la misma dirección de memoria. Dos objetos con los mismos campos, pero creados con new, serán distintos para ==.

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

System.out.println(p1 == p2); // false — ¡son objetos distintos en memoria!

Y si queremos saber si son iguales por contenido, usamos equals(), hashCode()... ya lo sabes. ¿Y si necesitamos entender quién es «mayor», «menor» o quién va «antes en el alfabeto»? Por ejemplo, para ordenar una lista de usuarios por edad o por nombre.

Necesidad de ordenar y buscar objetos

Supongamos que tenemos una lista de usuarios y queremos ordenarlos por edad:

List<Person> people = new ArrayList<>();
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 20));
people.add(new Person("David", 30));

// ¿Cómo ordenar?
Collections.sort(people); // ¡Ups! Java no sabe cómo comparar Person.

El compilador se quejará enseguida: la clase Person no implementa la interfaz Comparable. Java no lee la mente y no sabe qué significa para nosotros «mayor» o «menor» para Person. Para enseñárselo, debemos definir explícitamente las reglas de comparación.

2. Interfaz Comparable

Declaración de la interfaz

La interfaz Comparable es la manera estándar de decirle a Java: «Mi clase se puede comparar y así es como se hace».

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

El conocido a.compareTo(b) devuelve:

  • un número negativo — significa que a es «menor» que b.
  • Si es 0 — los objetos se consideran iguales.
  • un número positivoa es «mayor» que b.

Ejemplo: implementación de compareTo para la clase Person

Vamos a crear la clase Person, que se puede comparar por edad:

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

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

    // Getters (para los ejemplos de más adelante)
    public String getName() { return name; }
    public int getAge() { return age; }

    // Implementación del método compareTo
    @Override
    public int compareTo(Person other) {
        // Ordenación por edad (ascendente)
        return Integer.compare(this.age, other.age);
        // Alternativa: return this.age - other.age;
    }
}

Punto importante: si quieres ordenar en orden descendente, basta con intercambiar los argumentos: Integer.compare(other.age, this.age).

Analogía. compareTo es como un juez en una competición: debe decidir con claridad quién va delante, quién detrás y quién está al mismo nivel. Si cada juez (los métodos compareTo) juzga de forma distinta — ¡habrá caos!

3. Uso de Comparable

Ordenar colecciones con Comparable

Ahora que nuestra clase implementa Comparable, la ordenación funciona «de serie»:

List<Person> people = new ArrayList<>();
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 20));
people.add(new Person("David", 30));

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

for (Person p : people) {
    System.out.println(p.getName() + " (" + p.getAge() + ")");
}
// Charlie (20)
// Bob (25)
// David (30)

De forma análoga, el método sort de la lista:

people.sort(null); // Si se pasa null, se usa compareTo

Ordenación por nombre

Si queremos ordenar por nombre — cambiamos la implementación:

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

Ordenación por varios campos

A veces hay que comparar primero por un campo y, en caso de igualdad, por otro:

@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. Buenas prácticas al implementar Comparable

Cumpla el contrato de Comparable

  • Si a.compareTo(b) == 0, entonces b.compareTo(a) debe ser necesariamente 0.
  • Si a.compareTo(b) < 0, entonces b.compareTo(a) debe ser > 0 (y viceversa).
  • Si a.compareTo(b) == 0, es deseable que a.equals(b) sea true (aunque no es estrictamente obligatorio).

¿Por qué es importante?
Las colecciones (por ejemplo, TreeSet, TreeMap) y los métodos de ordenación pueden comportarse de forma impredecible si se viola el contrato. Por ejemplo, pueden aparecer «duplicados» en una colección donde no debería haberlos.

No olvide equals y hashCode

Si implementa compareTo, plantéese si equals y hashCode están bien implementados. Especialmente si su clase se va a usar en colecciones como 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);
}

¡No use en compareTo campos que puedan ser null sin comprobarlos!

Si un campo puede ser null, utilice comparaciones seguras:

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

No modifique los campos que participan en compareTo si el objeto ya está en una colección ordenada

Esto puede provocar que el objeto se «pierda» dentro de la colección — por ejemplo, en TreeSet o TreeMap.

5. Desarrollamos la aplicación de aprendizaje: ordenación de usuarios

Paso 1: Definir la clase

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

    // ... constructor, getters, compareTo, equals, hashCode ...
}

Paso 2: Añadimos usuarios

List<Person> people = new ArrayList<>();
people.add(new Person("Eve", 23));
people.add(new Person("Frank", 19));
people.add(new Person("Grace", 25));

Paso 3: Ordenar e imprimir

Collections.sort(people);

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

Resultado:

Frank (19)
Eve (23)
Grace (25)

6. Esquema de funcionamiento de Comparable

┌────────────────────────────┐
│   Tu clase (Person)        │
├────────────────────────────┤
│ implements Comparable      │
│   ↓                        │
│ public int compareTo(T o)  │
│   ↓                        │
│ (this < o) → -1            │
│ (this == o) → 0            │
│ (this > o) → 1             │
└────────────────────────────┘
        │
        ▼
Collections.sort(list)
        │
        ▼
   ¡La ordenación funciona!

7. Detalles útiles

Cómo funciona Collections.sort

  • Si la lista contiene objetos que implementan Comparable, la ordenación usará su método compareTo.
  • Si no lo implementa — habrá un error de compilación.
  • Para los tipos estándar (Integer, String, etc.) Comparable ya está implementado.

¿Se pueden tener varios modos de comparación?

  • En una clase — solo un «orden natural» a través de Comparable.
  • Para órdenes alternativos, use Comparator (siguiente lección).

Ejemplo: compareTo para cadenas

String a = "apple";
String b = "banana";
System.out.println(a.compareTo(b)); // número negativo, porque "apple" < "banana"

Tabla: qué devuelve compareTo

Comparación Valor devuelto
this < o
< 0
this == o
0
this > o
> 0

8. Errores típicos al implementar Comparable

Error n.º 1: Incumplir el contrato de compareTo.
Si a.compareTo(b) devuelve 0 y b.compareTo(a) no devuelve 0, las colecciones se comportarán de manera extraña. Por ejemplo, TreeSet puede considerar los objetos distintos y añadir ambos.

Error n.º 2: Usar campos no inicializados (null).
Si el campo por el que comparas puede ser null y no lo compruebas — obtendrás un NullPointerException.

Error n.º 3: Incoherencia entre compareTo y equals.
Si compareTo dice que los objetos son iguales (0) pero equals dice que son distintos (false), esto provocará errores al trabajar con colecciones.

Error n.º 4: Modificar los campos que participan en compareTo después de añadir a una colección ordenada.
Es como cambiarse el apellido en el pasaporte cuando ya estás en una cola ordenada alfabéticamente. La colección puede «perder» tu objeto.

Error n.º 5: Devolver solo -1, 0 o 1.
El método compareTo puede devolver cualquier número negativo o positivo, no necesariamente estrictamente -1 o 1. Aunque por simplicidad a menudo se usan -1/0/1.

1
Tarea
JAVA 25 SELF, nivel 29, lección 2
Bloqueada
Ordenación del catálogo de libros por varios criterios 📖
Ordenación del catálogo de libros por varios criterios 📖
1
Tarea
JAVA 25 SELF, nivel 29, lección 2
Bloqueada
Gestión de la unicidad y el ordenamiento de ciudades 🏙️
Gestión de la unicidad y el ordenamiento de ciudades 🏙️
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION