CodeGym /Cursos /JAVA 25 SELF /Interface Comparable: implementação, compareTo

Interface Comparable: implementação, compareTo

JAVA 25 SELF
Nível 29 , Lição 2
Disponível

1. O problema de comparar objetos

Lembrete: comparação de referências vs. comparação de objetos

Você já sabe que, em Java, o operador == ao trabalhar com objetos compara suas referências — isto é, se eles apontam para o mesmo endereço de memória. Dois objetos com os mesmos campos, mas criados com new, serão diferentes para ==.

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

System.out.println(p1 == p2); // false — são objetos diferentes na memória!

E se quisermos saber se eles são iguais pelo conteúdo, usamos equals(), hashCode() ... bem, você já sabe. Mas e se precisarmos entender quem é “mais velho”, “mais novo”, “vem antes no alfabeto”? Por exemplo, para ordenar uma lista de usuários por idade ou nome.

Necessidade de ordenar e buscar objetos

Suponha que temos uma lista de usuários e queremos ordená-los por idade:

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

// Como ordenar?
Collections.sort(people); // Opa! O Java não sabe como comparar Person!

O compilador vai reclamar imediatamente: a classe Person não implementa a interface Comparable. O Java não lê mentes e não sabe o que “maior” ou “menor” significa para Person. Para ensiná-lo, precisamos descrever explicitamente as regras de comparação.

2. Interface Comparable

Declaração da interface

A interface Comparable é a forma padrão de dizer ao Java: “Minha classe pode ser comparada, e é assim que se faz”.

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

O velho conhecido a.compareTo(b) vai retornar:

  • um número negativo — significa que a é “menor” que b.
  • Se for 0 — os objetos são considerados iguais.
  • um número positivoa é “maior” que b.

Exemplo: implementação de compareTo para a classe Person

Vamos criar a classe Person, que pode ser comparada por idade:

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 os exemplos a seguir)
    public String getName() { return name; }
    public int getAge() { return age; }

    // Implementação do método compareTo
    @Override
    public int compareTo(Person other) {
        // Ordenação por idade (crescente)
        return Integer.compare(this.age, other.age);
        // Alternativa: return this.age - other.age;
    }
}

Ponto importante: se você quiser ordenar em ordem decrescente, basta inverter os argumentos: Integer.compare(other.age, this.age).

Analogia. compareTo é como um juiz numa competição: ele deve decidir claramente quem está na frente, quem está atrás e quem está no mesmo nível. Se todos os juízes (métodos compareTo) julgarem de forma diferente — vira caos!

3. Usando Comparable

Ordenando coleções com Comparable

Agora que nossa classe implementa Comparable, a ordenação funciona “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)

O mesmo vale para o método sort da lista:

people.sort(null); // Se passar null, usa compareTo

Ordenação por nome

Se quisermos ordenar por nome — alteramos a implementação:

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

Ordenação por vários campos

Às vezes é preciso comparar primeiro por um campo e, em caso de empate, por outro:

@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. Boas práticas ao implementar Comparable

Respeite o contrato de Comparable

  • Se a.compareTo(b) == 0, então b.compareTo(a) obrigatoriamente deve ser 0.
  • Se a.compareTo(b) < 0, então b.compareTo(a) deve ser > 0 (e vice-versa).
  • Se a.compareTo(b) == 0, é desejável que a.equals(b) seja true (mas não é estritamente obrigatório).

Por que isso é importante?
Coleções (por exemplo, TreeSet, TreeMap) e métodos de ordenação podem se comportar de maneira imprevisível se o contrato for violado. Por exemplo, podem aparecer “duplicatas” em coleções onde não deveria haver.

Não se esqueça de equals e hashCode

Se você implementa compareTo, pense: equals e hashCode estão implementados corretamente? Especialmente se sua classe for usada em coleções como HashSet ou 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);
}

Não use em compareTo campos que podem ser null sem verificar!

Se um campo pode ser null, use comparações seguras:

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

Não altere os campos usados em compareTo se o objeto já estiver em uma coleção ordenada

Isso pode fazer com que o objeto “se perca” dentro da coleção — por exemplo, em TreeSet ou TreeMap.

5. Evoluindo o aplicativo didático: ordenação de usuários

Etapa 1: Descrevendo a classe

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

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

Etapa 2: Adicionando usuários

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

Etapa 3: Ordenamos e imprimimos

Collections.sort(people);

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

Resultado:

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

6. Esquema de funcionamento do Comparable

┌────────────────────────────┐
│   Sua classe (Person)      │
├────────────────────────────┤
│ implements Comparable      │
│   ↓                        │
│ public int compareTo(T o)  │
│   ↓                        │
│ (this < o) → -1            │
│ (this == o) → 0            │
│ (this > o) → 1             │
└────────────────────────────┘
        │
        ▼
Collections.sort(list)
        │
        ▼
   A ordenação funciona!

7. Nuances úteis

Como o Collections.sort funciona

  • Se a lista contém objetos que implementam Comparable, a ordenação usará o método compareTo deles.
  • Se não implementar — haverá erro de compilação.
  • Para tipos padrão (Integer, String etc.) Comparable já está implementado.

É possível ter vários modos de comparação?

  • Em uma classe — apenas uma “ordem natural” via Comparable.
  • Para ordens alternativas, use Comparator (próxima aula).

Exemplo: compareTo para strings

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

Tabela: o que compareTo retorna

Comparação Valor retornado
this < o
< 0
this == o
0
this > o
> 0

8. Erros comuns ao implementar Comparable

Erro nº 1: Violação do contrato de compareTo.
Se a.compareTo(b) retorna 0 e b.compareTo(a) — não 0, as coleções vão se comportar de forma estranha. Por exemplo, TreeSet pode considerar os objetos diferentes e adicionar ambos.

Erro nº 2: Uso de campos não inicializados (null).
Se o campo pelo qual você compara pode ser null e você não faz a verificação — vai receber NullPointerException.

Erro nº 3: Inconsistência entre compareTo e equals.
Se compareTo diz que os objetos são iguais (0), mas equals diz que são diferentes (false), isso levará a bugs ao trabalhar com coleções.

Erro nº 4: Alterar campos usados em compareTo após adicionar a uma coleção ordenada.
É como mudar o sobrenome no passaporte quando você já está numa fila alfabética. A coleção pode “perder” seu objeto.

Erro nº 5: Retornar apenas -1, 0 ou 1.
O método compareTo pode retornar qualquer número negativo ou positivo, não precisa ser estritamente -1 ou 1. Mas, por simplicidade, muitas vezes se usa -1/0/1.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION