CodeGym /Cursos /JAVA 25 SELF /Interface Comparator: criação e uso

Interface Comparator: criação e uso

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

1. Introdução

Na vida, raramente basta uma única maneira de comparar objetos. Imagine que você tem uma lista de usuários: às vezes você quer ordená-los por nome, às vezes – por idade, e às vezes – pelo comprimento do sobrenome. Ou você tem uma classe da qual você não é o autor, e não dá para adicionar compareTo a ela. É justamente para esses casos que existe, em Java, a interface Comparator.

Quando Comparable não é suficiente

  • A classe não pode ser modificada (por exemplo, ela vem de uma biblioteca de terceiros).
  • São necessários vários critérios de ordenação (por campos diferentes).
  • Você quer separar a lógica de comparação da própria classe (por exemplo, ordenar de formas diferentes em partes distintas do programa).

Analogia
Se Comparable é a “ordem natural” embutida do objeto, então Comparator é um árbitro externo que pode avaliar seus objetos por qualquer critério: hoje por nome, amanhã – por idade, depois de amanhã – pelo comprimento do nome.

2. Interface Comparator: sintaxe e contrato

Declaração da interface

public interface Comparator<T> {
    int compare(T o1, T o2);
}

O método compare deve retornar:

  • Número negativo, se o primeiro objeto for “menor” que o segundo.
  • 0, se forem iguais.
  • Número positivo, se o primeiro for “maior” que o segundo.

O contrato é o mesmo que o de Comparable; a diferença é que comparam-se dois objetos, e não “o atual” e “o outro” via compareTo.

Exemplo: comparador para ordenar por sobrenome

Suponha que temos a classe Person:

public class Person {
    private String firstName;
    private String lastName;
    private int age;

    // Construtor e getters
    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }
}

Vamos criar um comparador que ordena por sobrenome:

import java.util.Comparator;

public class LastNameComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return a.getLastName().compareTo(b.getLastName());
    }
}

Observação: o método compareTo em strings (String) as compara em ordem alfabética.

3. Uso de Comparator: ordenando coleções

Ordenação com comparador

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Person> people = new ArrayList<>();
        people.add(new Person("Anna", "Kostetskaya", 25));
        people.add(new Person("Boris", "Novak", 20));
        people.add(new Person("Viktoriya", "Bell", 22));

        // Ordenação por sobrenome
        Collections.sort(people, new LastNameComparator());

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

Resultado:

Novak Boris
Kostetskaya Anna
Bell Viktoriya

Ordenação por idade com um comparador

Mesmo se a classe já implementa Comparable por nome, é possível criar um comparador separado – por idade:

public class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person a, Person b) {
        return Integer.compare(a.getAge(), b.getAge());
    }
}

E usar de forma análoga:

Collections.sort(people, new AgeComparator());

Resultado:

Boris Novak (20)
Viktoriya Bell (22)
Anna Kostetskaya (25)

Exemplo: escolher o comparador “na hora”

Collections.sort(people, new LastNameComparator()); // Por sobrenome
Collections.sort(people, new AgeComparator());      // Por idade

4. Classes anônimas e expressões lambda

É possível criar comparadores “na hora”, sem declarar classes separadas.

Classe anônima

Collections.sort(people, new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return a.getFirstName().compareTo(b.getFirstName());
    }
});

Expressão lambda

Collections.sort(people, (a, b) -> a.getFirstName().compareTo(b.getFirstName()));

Ou ainda mais curto com o método da lista List.sort:

people.sort((a, b) -> a.getFirstName().compareTo(b.getFirstName()));
  • Classes anônimas – abordagem antiga, verbosa.
  • Lambda – moderno e compacto.

5. Exemplos: ordenação por diferentes critérios

Ordenação pelo comprimento do sobrenome

Comparator<Person> byLastNameLength = (a, b) ->
        Integer.compare(a.getLastName().length(), b.getLastName().length());
people.sort(byLastNameLength);

Ordenação por idade, depois por nome (múltiplos níveis)

Comparator<Person> byAgeThenName = (a, b) -> {
    int cmp = Integer.compare(a.getAge(), b.getAge());
    if (cmp != 0) return cmp;
    return a.getFirstName().compareTo(b.getFirstName());
};
people.sort(byAgeThenName);

Uso de um comparador para busca (exemplo)

O comparador se aplica não só à ordenação, mas também à busca em coleções já ordenadas:

// a lista people deve estar ordenada por idade!
Person key = new Person("?", "?", 22);
int idx = Collections.binarySearch(people, key, new AgeComparator());
if (idx >= 0) {
    System.out.println("Pessoa com 22 anos encontrada: " + people.get(idx));
}

6. Boas práticas e particularidades ao usar Comparator

Não viole o contrato

  • Se compare(a, b) retornar 0, então compare(b, a) também deve retornar 0.
  • Se compare(a, b) > 0, então compare(b, a) < 0.
  • Leve em conta possíveis valores null (veja abaixo).

Não se esqueça de equals e hashCode

Embora os comparadores comparem objetos “do seu jeito”, para estruturas como TreeSet ou ao buscar chaves em TreeMap é importante que a lógica de comparação do comparador esteja alinhada com equals. Caso contrário, podem surgir resultados inesperados: dois objetos diferentes são considerados iguais pelo comparador, mas não são iguais por equals.

Ordenação considerando null

Se os campos puderem ser null, use “helpers” prontos:

Comparator<Person> byLastNameNullSafe = Comparator.comparing(
    Person::getLastName,
    Comparator.nullsLast(String::compareTo)
);
people.sort(byLastNameNullSafe);

7. Nuances úteis

Tabela: Comparação de Comparable e Comparator

Comparable Comparator
Onde é implementado? Na própria classe Em classe separada/lambda
Método
int compareTo(T o)
int compare(T o1, T o2)
Quantas variações? Apenas uma “natural” Quantas forem necessárias, para quaisquer necessidades
Aplicação
Collections.sort(list)
Collections.sort(list, comp)
Funciona para classes de terceiros? Não Sim

Exemplo: ordenação em ordem decrescente

É possível inverter a ordem manualmente:

Comparator<Person> byAgeDesc = (a, b) -> Integer.compare(b.getAge(), a.getAge());
people.sort(byAgeDesc);

Ou com reversed():

Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);
people.sort(byAge.reversed());

8. Erros comuns ao trabalhar com Comparator

Erro nº 1: violação do contrato de comparação. Se você esquecer que compare(a, b) e compare(b, a) devem ter sinais opostos, ou se retornar valores arbitrários (por exemplo, apenas a diferença — a.getAge() - b.getAge(), o que pode causar overflow), o resultado será imprevisível. Use Integer.compare, e não subtração – é mais seguro.

Erro nº 2: ignorar valores null. Se os campos pelos quais você compara puderem ser null, trate esse caso (por exemplo, com Comparator.nullsFirst/Comparator.nullsLast), caso contrário é fácil obter NullPointerException no momento mais inesperado.

Erro nº 3: critérios de ordenação instáveis. Se o comparador retorna valores diferentes para os mesmos objetos (por exemplo, usa um número aleatório ou um campo altamente mutável), a ordenação pode se comportar de forma caótica.

Erro nº 4: inconsistência com equals. Se compare(a, b) == 0, mas a.equals(b) é false, coleções como TreeSet e TreeMap podem não funcionar como você espera. O ideal é que a igualdade pelo comparador e por equals coincidam.

Erro nº 5: ordenar classes de terceiros sem um comparador. Se você tentar ordenar objetos de uma classe “alheia” sem Comparable e sem fornecer um Comparator, obterá um erro de compilação. Forneça um comparador explícito.

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