Atualizado: 24/08/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Ordenando Objetos em Java: Interfaces Comparable e Comparator

Coleções ordenadas em Java, como a TreeSet, conseguem organizar elementos de tipos padrão (como String e Integer) automaticamente. Mas como uma coleção pode ordenar objetos de uma classe que você mesmo criou, como uma classe Person? Para que o Java entenda como comparar e classificar objetos personalizados, a classe precisa seguir um contrato de comparação. Esse contrato é definido através de duas interfaces fundamentais: Comparable e Comparator.

class Person {
    private String name;

    Person(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }
}

Não seria possível tipar um objeto TreeSet com essa classe, pois, ao adicionar objetos, o TreeSet não saberia como compará-los. O código a seguir não funcionaria:

TreeSet<Person> people = new TreeSet<>();
people.add(new Person("Tom"));

Ao executar esse código, ocorreria um erro indicando que o objeto Person não pode ser convertido para o tipo java.lang.Comparable.

Para que objetos Person possam ser comparados e ordenados, a classe deve implementar a interface Comparable<E>. Ao implementar, a interface é tipada com a classe atual. A implementação na classe Person fica assim:

class Person implements Comparable<Person> {
    private String name;

    Person(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public int compareTo(Person p) {
        return name.compareTo(p.getName());
    }
}

A interface Comparable contém apenas o método int compareTo(E item), que compara o objeto atual com o objeto passado como parâmetro. Se o método retornar um número negativo, o objeto atual fica antes do passado por parâmetro. Se retornar um número positivo, o objeto atual fica depois. Se retornar zero, os objetos são iguais.

Nesse caso, a comparação se baseia no mecanismo interno da classe String. Também é possível definir uma lógica própria, como comparar pelo comprimento do nome:

public int compareTo(Person p) {
    return name.length() - p.getName().length();
}

Agora, o TreeSet pode ser tipado com Person e objetos correspondentes podem ser adicionados:

TreeSet<Person> people = new TreeSet<>();
people.add(new Person("Tom"));

Interface Comparator

Pode surgir um problema se o desenvolvedor não implementou a interface Comparable na classe desejada, ou se a implementação existente não atende às necessidades e precisa ser alterada. Para isso, existe uma abordagem mais flexível com a interface Comparator<E>.

A interface Comparator contém vários métodos, mas o principal é o método compare():

public interface Comparator<E> {
    int compare(E a, E b);
    // outros métodos
}

O método compare retorna um valor numérico: negativo se o objeto a precede b, positivo se a segue b, e zero se são iguais. Para aplicar a interface, primeiro cria-se uma classe comparadora que a implementa:

class PersonComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
}

Aqui, a comparação ocorre por strings novamente. Agora, a classe comparadora é usada para criar o objeto TreeSet:

PersonComparator pcomp = new PersonComparator();
TreeSet<Person> people = new TreeSet<>(pcomp);
people.add(new Person("Tom"));
people.add(new Person("Nick"));
people.add(new Person("Alice"));
people.add(new Person("Bill"));

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

Para criar o TreeSet, usa-se uma versão do construtor que recebe o comparador como parâmetro. Independentemente de a classe Person implementar Comparable, a lógica de comparação e ordenação segue a definida na classe comparadora.

Ordenação por vários critérios

No Java, vários comparadores podem ser aplicados em sequência, com base em prioridade. Por exemplo, a classe Person é alterada para incluir idade:

class Person {
    private String name;
    private int age;

    public Person(String n, int a) {
        name = n;
        age = a;
    }

    String getName() {
        return name;
    }

    int getAge() {
        return age;
    }
}

Aqui, um campo armazena a idade do usuário. Suponha que os usuários precisam ser ordenados por nome e por idade. Para isso, definem-se dois comparadores:

class PersonNameComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
}

class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        if (a.getAge() > b.getAge()) {
            return 1;
        } else if (a.getAge() < b.getAge()) {
            return -1;
        } else {
            return 0;
        }
    }
}

A interface comparadora define o método padrão thenComparing, que permite encadear comparadores para ordenar o conjunto:

Comparator<Person> pcomp = new PersonNameComparator().thenComparing(new PersonAgeComparator());
TreeSet<Person> people = new TreeSet<>(pcomp);
people.add(new Person("Tom", 23));
people.add(new Person("Nick", 34));
people.add(new Person("Tom", 10));
people.add(new Person("Bill", 14));

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

A saída no console mostra:

Bill 14
Nick 34
Tom 10
Tom 23

Nesse caso, a ordenação ocorre primeiro por nome e depois por idade.

Resumo

  • Comparable: Interface implementada na classe para permitir comparação e ordenação natural, com o método compareTo.
  • Comparator: Interface flexível para definir comparações personalizadas, útil quando Comparable não está disponível ou precisa de ajuste.
  • Ordenação por vários critérios: O método thenComparing permite encadear comparadores, aplicando prioridades na ordenação.

📝 Exercícios

Tarefa 1: Ordenação Natural de Produtos (Usando Comparable)

Descrição: Você tem uma classe Produto. Faça com que ela implemente a interface Comparable para que os produtos sejam ordenados naturalmente pelo preço, do menor para o maior.

Código inicial:

import java.util.TreeSet;

class Produto implements Comparable<Produto> {
    private String nome;
    private double preco;

    public Produto(String nome, double preco) {
        this.nome = nome;
        this.preco = preco;
    }

    public double getPreco() { return this.preco; }
    public String getNome() { return this.nome; }

    @Override
    public String toString() {
        return "Produto: " + nome + ", Preço: R$" + preco;
    }

    // Implemente o método compareTo aqui

}

public class Main {
    public static void main(String[] args) {
        TreeSet<Produto> produtos = new TreeSet<>();
        produtos.add(new Produto("Caneta", 1.50));
        produtos.add(new Produto("Caderno", 15.00));
        produtos.add(new Produto("Borracha", 0.75));

        // A saída deve ser ordenada pelo preço
        for (Produto p : produtos) {
            System.out.println(p);
        }
    }
}
Resposta

Solução:

// Código completo para a classe Produto e Main
import java.util.TreeSet;

class Produto implements Comparable<Produto> {
    private String nome;
    private double preco;

    public Produto(String nome, double preco) {
        this.nome = nome;
        this.preco = preco;
    }

    public double getPreco() {
        return this.preco;
    }

    public String getNome() {
        return this.nome;
    }

    @Override
    public String toString() {
        return "Produto: " + nome + ", Preço: R$" + preco;
    }

    @Override
    public int compareTo(Produto outro) {
        // Double.compare é a forma segura de comparar doubles
        return Double.compare(this.preco, outro.getPreco());
    }
}

public class Main {
    public static void main(String[] args) {
        TreeSet<Produto> produtos = new TreeSet<>();
        produtos.add(new Produto("Caneta", 1.50));
        produtos.add(new Produto("Caderno", 15.00));
        produtos.add(new Produto("Borracha", 0.75));

        for (Produto p : produtos) {
            System.out.println(p);
        }
    }
}

Explicação da Solução: Ao implementar a interface Comparable<Produto>, somos obrigados a fornecer o método compareTo(). A forma mais segura e recomendada para comparar tipos primitivos numéricos (como double ou int) é usar o método estático compare de suas classes wrapper (Double.compare() ou Integer.compare()). Ele retorna -1, 0 ou 1, exatamente como o contrato de compareTo() exige, evitando problemas com subtração de ponto flutuante. Com isso, o TreeSet sabe como ordenar os produtos pelo preço.


Tarefa 2: Ordenação Alternativa de Alunos (Usando Comparator)

Descrição: A classe Aluno abaixo não pode ser modificada. Crie uma classe Comparator externa chamada ComparadorPorMedia que ordene os objetos Aluno pela média, da maior para a menor (ordem decrescente).

Código inicial:

import java.util.TreeSet;
import java.util.Comparator;

// VOCÊ NÃO PODE MODIFICAR ESTA CLASSE
class Aluno {
    private String nome;
    private double media;

    public Aluno(String nome, double media) {
        this.nome = nome;
        this.media = media;
    }
    public double getMedia() { return this.media; }
    public String getNome() { return this.nome; }

    @Override
    public String toString() {
        return "Aluno: " + nome + ", Média: " + media;
    }
}

// Crie a classe ComparadorPorMedia aqui

public class Main {
    public static void main(String[] args) {
        // Crie uma instância do seu comparador

        // Passe o comparador para o construtor do TreeSet
        TreeSet<Aluno> turma = new TreeSet<>(); // Mude esta linha

        turma.add(new Aluno("Carlos", 7.5));
        turma.add(new Aluno("Ana", 9.5));
        turma.add(new Aluno("Beatriz", 8.0));

        // A saída deve ser ordenada pela média, da maior para a menor
        for(Aluno a : turma) {
            System.out.println(a);
        }
    }
}
Resposta

Solução:

// Código completo para as classes Aluno, ComparadorPorMedia e Main
import java.util.TreeSet;
import java.util.Comparator;

class Aluno {
    private String nome;
    private double media;

    public Aluno(String nome, double media) {
        this.nome = nome;
        this.media = media;
    }
    public double getMedia() { return this.media; }
    public String getNome() { return this.nome; }

    @Override
    public String toString() {
        return "Aluno: " + nome + ", Média: " + media;
    }
}

// Comparator para ordenar Alunos pela média em ordem decrescente
class ComparadorPorMedia implements Comparator<Aluno> {
    @Override
    public int compare(Aluno a1, Aluno a2) {
        // Para ordem decrescente, invertemos a comparação
        return Double.compare(a2.getMedia(), a1.getMedia());
    }
}

public class Main {
    public static void main(String[] args) {
        ComparadorPorMedia comparador = new ComparadorPorMedia();
        TreeSet<Aluno> turma = new TreeSet<>(comparador);

        turma.add(new Aluno("Carlos", 7.5));
        turma.add(new Aluno("Ana", 9.5));
        turma.add(new Aluno("Beatriz", 8.0));

        for(Aluno a : turma) {
            System.out.println(a);
        }
    }
}

Explicação da Solução: Como não podíamos alterar a classe Aluno, a solução foi criar uma classe externa, ComparadorPorMedia, que implementa Comparator<Aluno>. Dentro do método compare, usamos Double.compare(a2.getMedia(), a1.getMedia()). Note a inversão de a1 e a2: comparar a2 com a1 em vez de a1 com a2 é um truque simples para obter a ordem decrescente. Finalmente, uma instância do nosso comparador é passada para o construtor do TreeSet, que passa a usar essa lógica para ordenar os alunos.


Tarefa 3: Ordenação por Múltiplos Critérios

Descrição: Você precisa ordenar uma lista de funcionários. O critério principal é o departamento (em ordem alfabética). Se os funcionários forem do mesmo departamento, o critério de desempate é o salário (do maior para o menor). Use Comparator.thenComparing() para combinar as lógicas.

Código inicial:

import java.util.TreeSet;
import java.util.Comparator;

class Funcionario {
    private String nome;
    private String departamento;
    private double salario;

    public Funcionario(String n, String d, double s) {
        this.nome = n;
        this.departamento = d;
        this.salario = s;
    }
    public String getNome() { return nome; }
    public String getDepartamento() { return departamento; }
    public double getSalario() { return salario; }

    @Override
    public String toString() {
        return "Dep: " + departamento + ", Salário: " + salario + ", Nome: " + nome;
    }

}

public class Main {
    public static void main(String[] args) {
        Comparator<Funcionario> comparadorFinal = null;

        // Crie o comparador combinado aqui

        TreeSet<Funcionario> funcionarios = new TreeSet<>(comparadorFinal);
        funcionarios.add(new Funcionario("Ana", "TI", 5000));
        funcionarios.add(new Funcionario("Carlos", "RH", 4500));
        funcionarios.add(new Funcionario("Beatriz", "TI", 6000));

        for(Funcionario f : funcionarios) {
            System.out.println(f);
        }
    }
}
Resposta

Solução:

// Código completo para as classes Funcionario e Main
import java.util.TreeSet;
import java.util.Comparator;

class Funcionario {
    private String nome;
    private String departamento;
    private double salario;

    public Funcionario(String n, String d, double s) {
        this.nome = n;
        this.departamento = d;
        this.salario = s;
    }
    public String getNome() { return nome; }
    public String getDepartamento() { return departamento; }
    public double getSalario() { return salario; }

    @Override
    public String toString() {
        return "Dep: " + departamento + ", Salário: " + salario + ", Nome: " + nome;
    }
}

public class Main {
    public static void main(String[] args) {
        // Critério 1: Departamento (ordem natural da String)
        Comparator<Funcionario> porDepartamento =
            Comparator.comparing(Funcionario::getDepartamento);

        // Critério 2: Salário (ordem decrescente)
        Comparator<Funcionario> porSalarioDesc =
            Comparator.comparingDouble(Funcionario::getSalario).reversed();

        // Combinando os dois comparadores
        Comparator<Funcionario> comparadorFinal =
            porDepartamento.thenComparing(porSalarioDesc);

        TreeSet<Funcionario> funcionarios = new TreeSet<>(comparadorFinal);
        funcionarios.add(new Funcionario("Ana", "TI", 5000));
        funcionarios.add(new Funcionario("Carlos", "RH", 4500));
        funcionarios.add(new Funcionario("Beatriz", "TI", 6000));

        for(Funcionario f : funcionarios) {
            System.out.println(f);
        }
    }
}

Explicação da Solução: Esta solução usa os métodos de fábrica do Comparator, que são muito mais concisos.

  • Comparator.comparing(Funcionario::getDepartamento): Cria um Comparator que ordena os funcionários com base no valor retornado por getDepartamento(), usando a ordem natural (alfabética).
  • Comparator.comparingDouble(Funcionario::getSalario).reversed(): Cria um Comparator para double e, em seguida, usa o método .reversed() para inverter a ordem natural (crescente -> decrescente).
  • .thenComparing(porSalarioDesc): Encadeia os dois. O TreeSet usará porDepartamento como critério principal. Somente se dois funcionários tiverem o mesmo departamento (como Ana e Beatriz), ele usará porSalarioDesc para decidir a ordem entre eles.
  • Política de Privacidade

    Copyright © www.programicio.com Todos os direitos reservados

    É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

    Contato: programicio@gmail.com