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 positivo — a é “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 |
|---|---|
|
|
|
|
|
|
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.
GO TO FULL VERSION