Como comparar objetos Java com base em múltiplos atributos

JavaBeginner
Pratique Agora

Introdução

Comparar objetos Java com base em múltiplos atributos é uma habilidade essencial para qualquer desenvolvedor Java. Essa capacidade permite a ordenação, filtragem e organização eficientes de dados em aplicações. Neste laboratório, você aprenderá como implementar diferentes mecanismos de comparação em Java para lidar com objetos com múltiplas propriedades.

Exploraremos as abordagens fundamentais para comparar objetos em Java, incluindo a sobrescrita do método equals(), a implementação da interface Comparable e o uso da interface Comparator. Através de exemplos práticos e exercícios práticos, você obterá uma compreensão sólida de quando e como usar cada abordagem.

Criando uma Classe Estudante com Múltiplos Atributos

Nesta primeira etapa, criaremos uma classe Java simples para representar um estudante com múltiplos atributos. Isso servirá como nossa base para aprender sobre comparação de objetos em Java.

Configurando a Estrutura do Projeto

Vamos começar criando um diretório para nosso projeto e navegando até ele:

mkdir -p ~/project/java-comparison
cd ~/project/java-comparison

Criando a Classe Estudante

Agora, vamos criar uma classe Student com múltiplos atributos, como nome, idade e nota. Abra o WebIDE e crie um novo arquivo chamado Student.java no diretório do projeto com o seguinte conteúdo:

public class Student {
    private String name;
    private int age;
    private double grade;

    // Constructor
    public Student(String name, int age, double grade) {
        this.name = name;
        this.age = age;
        this.grade = grade;
    }

    // Getters
    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public double getGrade() {
        return grade;
    }

    // toString method for easy display
    @Override
    public String toString() {
        return "Student{name='" + name + "', age=" + age + ", grade=" + grade + "}";
    }
}

Esta classe representa um estudante com três atributos: name (uma String), age (um inteiro) e grade (um double). Também incluímos um construtor, getters para cada atributo e um método toString() para facilitar a exibição das informações do estudante.

Testando a Classe Estudante

Vamos criar uma classe principal para testar nossa classe Student. Crie um novo arquivo chamado StudentTest.java com o seguinte conteúdo:

public class StudentTest {
    public static void main(String[] args) {
        // Create some student objects
        Student alice = new Student("Alice", 20, 85.5);
        Student bob = new Student("Bob", 22, 90.0);
        Student charlie = new Student("Charlie", 19, 78.3);

        // Display student information
        System.out.println("Student 1: " + alice);
        System.out.println("Student 2: " + bob);
        System.out.println("Student 3: " + charlie);

        // Compare two students using == (identity comparison)
        Student aliceCopy = alice;
        System.out.println("\nIdentity comparison (==):");
        System.out.println("alice == bob: " + (alice == bob));
        System.out.println("alice == aliceCopy: " + (alice == aliceCopy));

        // Try to compare students with built-in methods
        System.out.println("\nNote: At this point, we cannot properly compare students based on their attributes.");
        System.out.println("We will implement proper comparison in the next steps.");
    }
}

Compilar e Executar

Agora vamos compilar e executar nosso código:

javac StudentTest.java
java StudentTest

Você deve ver uma saída semelhante a esta:

Student 1: Student{name='Alice', age=20, grade=85.5}
Student 2: Student{name='Bob', age=22, grade=90.0}
Student 3: Student{name='Charlie', age=19, grade=78.3}

Identity comparison (==):
alice == bob: false
alice == aliceCopy: true

Note: At this point, we cannot properly compare students based on their attributes.
We will implement proper comparison in the next steps.

Entendendo os Conceitos Básicos de Comparação de Objetos

Observe em nosso teste que usamos o operador == para comparar dois objetos Student. Isso é chamado de comparação de identidade em Java, que verifica se duas referências apontam para o mesmo objeto na memória.

No entanto, frequentemente precisamos comparar objetos com base em seu conteúdo ou atributos, o que é chamado de comparação de igualdade. Java fornece vários mecanismos para esse fim:

  1. Sobrescrever o método equals()
  2. Implementar a interface Comparable
  3. Usar a interface Comparator

Nas próximas etapas, implementaremos esses mecanismos para comparar objetos Student com base em seus atributos.

Implementando os Métodos equals() e hashCode()

Antes de mergulharmos nas interfaces Comparable e Comparator, vamos primeiro implementar os métodos equals() e hashCode() em nossa classe Student. Esses métodos são fundamentais para a comparação adequada de objetos em Java.

Entendendo equals() e hashCode()

Em Java, o método equals() é usado para verificar se dois objetos são iguais com base em seu conteúdo, enquanto o método hashCode() gera um valor numérico que representa o objeto. Esses dois métodos trabalham juntos, especialmente quando os objetos são armazenados em coleções como HashMap ou HashSet.

Uma implementação adequada deve seguir estas regras:

  • Se dois objetos são iguais de acordo com equals(), eles devem ter o mesmo código hash.
  • Se dois objetos têm o mesmo código hash, eles não são necessariamente iguais.

Atualizando a Classe Student

Vamos atualizar nossa classe Student para sobrescrever esses métodos. Abra Student.java e adicione os seguintes métodos:

public class Student {
    private String name;
    private int age;
    private double grade;

    // Existing constructor and getters...

    // Existing toString method...

    // Override equals method
    @Override
    public boolean equals(Object obj) {
        // Check if same object reference
        if (this == obj) return true;
        // Check if null or different class
        if (obj == null || getClass() != obj.getClass()) return false;

        // Cast to Student
        Student other = (Student) obj;

        // Compare attributes
        return age == other.age &&
               Double.compare(grade, other.grade) == 0 &&
               (name == null ? other.name == null : name.equals(other.name));
    }

    // Override hashCode method
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        result = 31 * result + (int) (Double.doubleToLongBits(grade) ^ (Double.doubleToLongBits(grade) >>> 32));
        return result;
    }
}

O método equals() segue um padrão padrão:

  1. Verifique se o objeto está sendo comparado a si mesmo
  2. Verifique se o objeto é nulo ou de uma classe diferente
  3. Converta o objeto para o tipo apropriado
  4. Compare cada atributo para igualdade

O método hashCode() combina os códigos hash de todos os atributos para gerar um código hash exclusivo para o objeto.

Testando equals() e hashCode()

Agora, vamos atualizar nosso arquivo StudentTest.java para testar esses métodos:

public class StudentTest {
    public static void main(String[] args) {
        // Create some student objects
        Student alice = new Student("Alice", 20, 85.5);
        Student bob = new Student("Bob", 22, 90.0);
        Student aliceDuplicate = new Student("Alice", 20, 85.5); // Same attributes as Alice

        // Display student information
        System.out.println("Student 1: " + alice);
        System.out.println("Student 2: " + bob);
        System.out.println("Student 3 (Alice duplicate): " + aliceDuplicate);

        // Identity comparison (==)
        System.out.println("\nIdentity comparison (==):");
        System.out.println("alice == bob: " + (alice == bob));
        System.out.println("alice == aliceDuplicate: " + (alice == aliceDuplicate));

        // Equality comparison (equals())
        System.out.println("\nEquality comparison (equals()):");
        System.out.println("alice.equals(bob): " + alice.equals(bob));
        System.out.println("alice.equals(aliceDuplicate): " + alice.equals(aliceDuplicate));

        // Hash code comparison
        System.out.println("\nHash code comparison:");
        System.out.println("alice.hashCode(): " + alice.hashCode());
        System.out.println("bob.hashCode(): " + bob.hashCode());
        System.out.println("aliceDuplicate.hashCode(): " + aliceDuplicate.hashCode());
    }
}

Compilar e Executar

Vamos compilar e executar nosso código atualizado:

javac Student.java StudentTest.java
java StudentTest

A saída deve ser semelhante a:

Student 1: Student{name='Alice', age=20, grade=85.5}
Student 2: Student{name='Bob', age=22, grade=90.0}
Student 3 (Alice duplicate): Student{name='Alice', age=20, grade=85.5}

Identity comparison (==):
alice == bob: false
alice == aliceDuplicate: false

Equality comparison (equals()):
alice.equals(bob): false
alice.equals(aliceDuplicate): true

Hash code comparison:
alice.hashCode(): 62509338
bob.hashCode(): 62565066
aliceDuplicate.hashCode(): 62509338

Observe que, embora alice e aliceDuplicate sejam objetos diferentes (como mostrado pela comparação ==), eles são considerados iguais de acordo com nosso método equals() porque têm os mesmos valores de atributo. Além disso, eles têm o mesmo código hash, conforme exigido pelo contrato entre equals() e hashCode().

Embora equals() seja útil para determinar se dois objetos são iguais, ele não nos ajuda a estabelecer uma ordenação entre os objetos (como qual deve vir primeiro em uma lista ordenada). Para isso, precisamos da interface Comparable, que implementaremos na próxima etapa.

Implementando a Interface Comparable

O método equals() nos permite verificar se dois objetos são iguais, mas não nos ajuda a estabelecer uma ordenação. Para isso, Java fornece a interface Comparable, que nos permite definir uma "ordenação natural" para nossos objetos.

Entendendo a Interface Comparable

A interface Comparable<T> possui um único método, compareTo(T o), que compara o objeto atual com outro objeto do mesmo tipo. O método retorna:

  • Um inteiro negativo se o objeto atual for menor que o outro objeto
  • Zero se o objeto atual for igual ao outro objeto
  • Um inteiro positivo se o objeto atual for maior que o outro objeto

Ao implementar essa interface, podemos definir como os objetos de nossa classe devem ser ordenados.

Atualizando a Classe Student

Vamos atualizar nossa classe Student para implementar a interface Comparable. Definiremos a ordenação natural com base na nota do aluno (notas mais altas vêm primeiro), depois pela idade (alunos mais jovens vêm primeiro) e, finalmente, pelo nome (ordem alfabética).

Atualize seu arquivo Student.java:

public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private double grade;

    // Existing constructor, getters, toString, equals, and hashCode methods...

    // Implement compareTo method from Comparable interface
    @Override
    public int compareTo(Student other) {
        // Compare by grade (descending order)
        if (Double.compare(other.grade, this.grade) != 0) {
            return Double.compare(other.grade, this.grade);
        }

        // If grades are equal, compare by age (ascending order)
        if (this.age != other.age) {
            return Integer.compare(this.age, other.age);
        }

        // If grades and ages are equal, compare by name (alphabetical order)
        return this.name.compareTo(other.name);
    }
}

Criando um Teste para a Implementação Comparable

Vamos criar um novo arquivo ComparableTest.java para testar nossa implementação Comparable:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ComparableTest {
    public static void main(String[] args) {
        // Create a list of students
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 20, 85.5));
        students.add(new Student("Bob", 22, 90.0));
        students.add(new Student("Charlie", 19, 78.3));
        students.add(new Student("David", 21, 85.5));
        students.add(new Student("Eve", 20, 92.7));

        // Display unsorted list
        System.out.println("Unsorted list of students:");
        for (Student student : students) {
            System.out.println(student);
        }

        // Sort the list using the natural ordering defined by Comparable
        Collections.sort(students);

        // Display sorted list
        System.out.println("\nSorted list of students (by grade descending, then age ascending, then name):");
        for (Student student : students) {
            System.out.println(student);
        }

        // Let's compare some students directly using compareTo
        Student alice = students.get(3); // Alice should be at index 3 after sorting
        Student bob = students.get(1);   // Bob should be at index 1 after sorting

        System.out.println("\nComparing students directly using compareTo:");
        int comparison = alice.compareTo(bob);
        System.out.println("alice.compareTo(bob) = " + comparison);

        if (comparison < 0) {
            System.out.println("Alice comes before Bob in the natural ordering");
        } else if (comparison > 0) {
            System.out.println("Bob comes before Alice in the natural ordering");
        } else {
            System.out.println("Alice and Bob are equal in the natural ordering");
        }
    }
}

Compilar e Executar

Vamos compilar e executar nosso código:

javac Student.java ComparableTest.java
java ComparableTest

A saída deve ser semelhante a:

Unsorted list of students:
Student{name='Alice', age=20, grade=85.5}
Student{name='Bob', age=22, grade=90.0}
Student{name='Charlie', age=19, grade=78.3}
Student{name='David', age=21, grade=85.5}
Student{name='Eve', age=20, grade=92.7}

Sorted list of students (by grade descending, then age ascending, then name):
Student{name='Eve', age=20, grade=92.7}
Student{name='Bob', age=22, grade=90.0}
Student{name='Alice', age=20, grade=85.5}
Student{name='David', age=21, grade=85.5}
Student{name='Charlie', age=19, grade=78.3}

Comparing students directly using compareTo:
alice.compareTo(bob) = 1
Bob comes before Alice in the natural ordering

Observe que os alunos agora estão classificados por sua nota em ordem decrescente. Quando as notas são iguais (como com Alice e David, ambos com 85,5), eles são classificados por idade em ordem crescente.

A interface Comparable fornece uma ordenação natural para nossos objetos, mas e se quisermos classificar objetos de maneiras diferentes em momentos diferentes? Por exemplo, às vezes podemos querer classificar os alunos por nome e, outras vezes, por idade. É aí que a interface Comparator entra em ação, que exploraremos na próxima etapa.

Usando Comparator para Ordenação Personalizada

A interface Comparable fornece uma ordenação natural para nossa classe, mas às vezes precisamos classificar objetos de maneiras diferentes com base em critérios diferentes. É aqui que a interface Comparator se torna útil.

Entendendo a Interface Comparator

A interface Comparator<T> define um método compare(T o1, T o2) que compara dois objetos do mesmo tipo. Ao contrário de Comparable, que é implementado pela própria classe que está sendo comparada, um Comparator é uma classe separada ou expressão lambda que pode definir vários critérios de ordenação.

Criando Comparadores Personalizados

Vamos criar vários comparadores para nossa classe Student:

  1. NameComparator: Classifica os alunos por nome (alfabeticamente)
  2. AgeComparator: Classifica os alunos por idade (crescente)
  3. GradeComparator: Classifica os alunos por nota (decrescente)

Crie um novo arquivo chamado StudentComparators.java com o seguinte conteúdo:

import java.util.Comparator;

public class StudentComparators {
    // Comparator for sorting students by name
    public static class NameComparator implements Comparator<Student> {
        @Override
        public int compare(Student s1, Student s2) {
            return s1.getName().compareTo(s2.getName());
        }
    }

    // Comparator for sorting students by age
    public static class AgeComparator implements Comparator<Student> {
        @Override
        public int compare(Student s1, Student s2) {
            return Integer.compare(s1.getAge(), s2.getAge());
        }
    }

    // Comparator for sorting students by grade (descending)
    public static class GradeComparator implements Comparator<Student> {
        @Override
        public int compare(Student s1, Student s2) {
            return Double.compare(s2.getGrade(), s1.getGrade());
        }
    }

    // Multi-attribute comparator: sort by grade (descending), then by name (alphabetically)
    public static class GradeNameComparator implements Comparator<Student> {
        @Override
        public int compare(Student s1, Student s2) {
            // First compare by grade (descending)
            int gradeComparison = Double.compare(s2.getGrade(), s1.getGrade());
            if (gradeComparison != 0) {
                return gradeComparison;
            }

            // If grades are equal, compare by name (alphabetically)
            return s1.getName().compareTo(s2.getName());
        }
    }
}

Testando os Comparadores

Agora, vamos criar uma classe de teste para demonstrar como usar esses comparadores. Crie um arquivo chamado ComparatorTest.java:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Comparator;

public class ComparatorTest {
    public static void main(String[] args) {
        // Create a list of students
        List<Student> students = new ArrayList<>();
        students.add(new Student("Alice", 20, 85.5));
        students.add(new Student("Bob", 22, 90.0));
        students.add(new Student("Charlie", 19, 78.3));
        students.add(new Student("David", 21, 85.5));
        students.add(new Student("Eve", 20, 92.7));

        // Display original list
        System.out.println("Original list of students:");
        printStudents(students);

        // Sort by name using NameComparator
        Collections.sort(students, new StudentComparators.NameComparator());
        System.out.println("\nStudents sorted by name:");
        printStudents(students);

        // Sort by age using AgeComparator
        Collections.sort(students, new StudentComparators.AgeComparator());
        System.out.println("\nStudents sorted by age (ascending):");
        printStudents(students);

        // Sort by grade using GradeComparator
        Collections.sort(students, new StudentComparators.GradeComparator());
        System.out.println("\nStudents sorted by grade (descending):");
        printStudents(students);

        // Sort by grade, then by name using GradeNameComparator
        Collections.sort(students, new StudentComparators.GradeNameComparator());
        System.out.println("\nStudents sorted by grade (descending), then by name:");
        printStudents(students);

        // Using Java 8 lambda expressions for comparators
        System.out.println("\nUsing Java 8 lambda expressions:");

        // Sort by name (alphabetically) using lambda
        Collections.sort(students, (s1, s2) -> s1.getName().compareTo(s2.getName()));
        System.out.println("\nStudents sorted by name (using lambda):");
        printStudents(students);

        // Sort by age (descending) using lambda
        Collections.sort(students, (s1, s2) -> Integer.compare(s2.getAge(), s1.getAge()));
        System.out.println("\nStudents sorted by age (descending, using lambda):");
        printStudents(students);

        // Using Comparator.comparing method
        System.out.println("\nUsing Comparator.comparing method:");

        // Sort by name
        Collections.sort(students, Comparator.comparing(Student::getName));
        System.out.println("\nStudents sorted by name (using Comparator.comparing):");
        printStudents(students);

        // Sort by grade (descending), then by age (ascending), then by name
        Collections.sort(students,
            Comparator.comparing(Student::getGrade, Comparator.reverseOrder())
                      .thenComparing(Student::getAge)
                      .thenComparing(Student::getName));
        System.out.println("\nStudents sorted by grade (desc), then age (asc), then name:");
        printStudents(students);
    }

    // Helper method to print the list of students
    private static void printStudents(List<Student> students) {
        for (Student student : students) {
            System.out.println(student);
        }
    }
}

Compilar e Executar

Vamos compilar e executar nosso código:

javac Student.java StudentComparators.java ComparatorTest.java
java ComparatorTest

A saída demonstrará como os diferentes comparadores afetam a ordem de classificação dos alunos:

Original list of students:
Student{name='Alice', age=20, grade=85.5}
Student{name='Bob', age=22, grade=90.0}
Student{name='Charlie', age=19, grade=78.3}
Student{name='David', age=21, grade=85.5}
Student{name='Eve', age=20, grade=92.7}

Students sorted by name:
Student{name='Alice', age=20, grade=85.5}
Student{name='Bob', age=22, grade=90.0}
Student{name='Charlie', age=19, grade=78.3}
Student{name='David', age=21, grade=85.5}
Student{name='Eve', age=20, grade=92.7}

... (and so on for the other sorting methods)

Principais Diferenças entre Comparable e Comparator

Entender as diferenças entre Comparable e Comparator é importante:

  1. Localização da implementação:

    • Comparable é implementado pela própria classe.
    • Comparator é implementado em uma classe separada ou como uma expressão lambda.
  2. Número de ordenações:

    • Comparable define uma única "ordenação natural" para uma classe.
    • Comparator permite várias ordenações diferentes.
  3. Assinaturas de métodos:

    • Comparable tem int compareTo(T o)
    • Comparator tem int compare(T o1, T o2)
  4. Uso:

    • Comparable é mais simples quando há uma ordenação natural óbvia.
    • Comparator é mais flexível quando várias ordenações são necessárias.

Na etapa final, criaremos uma aplicação do mundo real que usa Comparable e Comparator para gerenciar um banco de dados de alunos.

Construindo um Sistema de Gerenciamento de Alunos

Agora que entendemos como comparar objetos usando equals(), Comparable e Comparator, vamos construir um sistema simples de gerenciamento de alunos que reúne tudo em uma aplicação prática.

Criando a Classe StudentManager

Vamos criar uma classe que irá gerenciar uma coleção de alunos e fornecer várias operações sobre eles. Crie um arquivo chamado StudentManager.java:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

public class StudentManager {
    private List<Student> students;

    public StudentManager() {
        students = new ArrayList<>();
    }

    // Add a student to the list
    public void addStudent(Student student) {
        students.add(student);
    }

    // Get all students
    public List<Student> getAllStudents() {
        return new ArrayList<>(students); // Return a copy to prevent modification
    }

    // Get top students by grade
    public List<Student> getTopStudents(int count) {
        List<Student> sortedStudents = new ArrayList<>(students);
        Collections.sort(sortedStudents, new StudentComparators.GradeComparator());

        // Return the top 'count' students or all if there are fewer
        return sortedStudents.subList(0, Math.min(count, sortedStudents.size()));
    }

    // Get students sorted by name
    public List<Student> getStudentsSortedByName() {
        List<Student> sortedStudents = new ArrayList<>(students);
        Collections.sort(sortedStudents, new StudentComparators.NameComparator());
        return sortedStudents;
    }

    // Get students sorted by age
    public List<Student> getStudentsSortedByAge() {
        List<Student> sortedStudents = new ArrayList<>(students);
        Collections.sort(sortedStudents, new StudentComparators.AgeComparator());
        return sortedStudents;
    }

    // Get students with grade above threshold
    public List<Student> getStudentsAboveGrade(double threshold) {
        List<Student> result = new ArrayList<>();
        for (Student student : students) {
            if (student.getGrade() > threshold) {
                result.add(student);
            }
        }
        return result;
    }

    // Get students grouped by age
    public Map<Integer, List<Student>> getStudentsGroupedByAge() {
        Map<Integer, List<Student>> map = new HashMap<>();

        for (Student student : students) {
            int age = student.getAge();
            if (!map.containsKey(age)) {
                map.put(age, new ArrayList<>());
            }
            map.get(age).add(student);
        }

        return map;
    }

    // Find a student by name (returns null if not found)
    public Student findStudentByName(String name) {
        for (Student student : students) {
            if (student.getName().equals(name)) {
                return student;
            }
        }
        return null;
    }

    // Get student count
    public int getStudentCount() {
        return students.size();
    }
}

Criando a Aplicação Principal

Agora, vamos criar nossa aplicação principal que usa a classe StudentManager. Crie um arquivo chamado StudentManagementApp.java:

import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class StudentManagementApp {
    private static StudentManager manager = new StudentManager();
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        // Add some sample students
        initializeData();

        boolean running = true;
        while (running) {
            displayMenu();
            int choice = getIntInput("Enter your choice: ");

            switch (choice) {
                case 1:
                    addStudent();
                    break;
                case 2:
                    displayAllStudents();
                    break;
                case 3:
                    displayTopStudents();
                    break;
                case 4:
                    displayStudentsSortedByName();
                    break;
                case 5:
                    displayStudentsSortedByAge();
                    break;
                case 6:
                    displayStudentsAboveGrade();
                    break;
                case 7:
                    displayStudentsGroupedByAge();
                    break;
                case 8:
                    findStudent();
                    break;
                case 9:
                    running = false;
                    break;
                default:
                    System.out.println("Invalid choice. Please try again.");
            }

            System.out.println(); // Empty line for better readability
        }

        System.out.println("Thank you for using the Student Management System.");
        scanner.close();
    }

    private static void displayMenu() {
        System.out.println("===== Student Management System =====");
        System.out.println("1. Add a new student");
        System.out.println("2. Display all students");
        System.out.println("3. Display top students by grade");
        System.out.println("4. Display students sorted by name");
        System.out.println("5. Display students sorted by age");
        System.out.println("6. Display students above a grade threshold");
        System.out.println("7. Display students grouped by age");
        System.out.println("8. Find a student by name");
        System.out.println("9. Exit");
    }

    private static void initializeData() {
        manager.addStudent(new Student("Alice", 20, 85.5));
        manager.addStudent(new Student("Bob", 22, 90.0));
        manager.addStudent(new Student("Charlie", 19, 78.3));
        manager.addStudent(new Student("David", 21, 85.5));
        manager.addStudent(new Student("Eve", 20, 92.7));
    }

    private static void addStudent() {
        System.out.println("=== Add a new student ===");
        String name = getStringInput("Enter student name: ");
        int age = getIntInput("Enter student age: ");
        double grade = getDoubleInput("Enter student grade: ");

        manager.addStudent(new Student(name, age, grade));
        System.out.println("Student added successfully.");
    }

    private static void displayAllStudents() {
        System.out.println("=== All Students ===");
        List<Student> students = manager.getAllStudents();
        displayStudents(students);
    }

    private static void displayTopStudents() {
        int count = getIntInput("Enter the number of top students to display: ");
        System.out.println("=== Top " + count + " Students ===");
        List<Student> topStudents = manager.getTopStudents(count);
        displayStudents(topStudents);
    }

    private static void displayStudentsSortedByName() {
        System.out.println("=== Students Sorted by Name ===");
        List<Student> sortedStudents = manager.getStudentsSortedByName();
        displayStudents(sortedStudents);
    }

    private static void displayStudentsSortedByAge() {
        System.out.println("=== Students Sorted by Age ===");
        List<Student> sortedStudents = manager.getStudentsSortedByAge();
        displayStudents(sortedStudents);
    }

    private static void displayStudentsAboveGrade() {
        double threshold = getDoubleInput("Enter the grade threshold: ");
        System.out.println("=== Students Above Grade " + threshold + " ===");
        List<Student> filteredStudents = manager.getStudentsAboveGrade(threshold);
        displayStudents(filteredStudents);
    }

    private static void displayStudentsGroupedByAge() {
        System.out.println("=== Students Grouped by Age ===");
        Map<Integer, List<Student>> groupedStudents = manager.getStudentsGroupedByAge();

        for (Map.Entry<Integer, List<Student>> entry : groupedStudents.entrySet()) {
            System.out.println("Age " + entry.getKey() + ":");
            displayStudents(entry.getValue());
            System.out.println();
        }
    }

    private static void findStudent() {
        String name = getStringInput("Enter the name of the student to find: ");
        Student student = manager.findStudentByName(name);

        if (student != null) {
            System.out.println("=== Student Found ===");
            System.out.println(student);
        } else {
            System.out.println("Student with name '" + name + "' not found.");
        }
    }

    private static void displayStudents(List<Student> students) {
        if (students.isEmpty()) {
            System.out.println("No students to display.");
            return;
        }

        for (Student student : students) {
            System.out.println(student);
        }
    }

    private static String getStringInput(String prompt) {
        System.out.print(prompt);
        return scanner.nextLine();
    }

    private static int getIntInput(String prompt) {
        while (true) {
            try {
                System.out.print(prompt);
                String input = scanner.nextLine();
                return Integer.parseInt(input);
            } catch (NumberFormatException e) {
                System.out.println("Invalid input. Please enter a valid integer.");
            }
        }
    }

    private static double getDoubleInput(String prompt) {
        while (true) {
            try {
                System.out.print(prompt);
                String input = scanner.nextLine();
                return Double.parseDouble(input);
            } catch (NumberFormatException e) {
                System.out.println("Invalid input. Please enter a valid number.");
            }
        }
    }
}

Compilar e Executar a Aplicação

Vamos compilar e executar nosso sistema de gerenciamento de alunos:

javac Student.java StudentComparators.java StudentManager.java StudentManagementApp.java
java StudentManagementApp

Você verá agora um menu interativo que permite:

  1. Adicionar novos alunos
  2. Exibir todos os alunos
  3. Exibir os melhores alunos por nota
  4. Exibir alunos classificados por nome
  5. Exibir alunos classificados por idade
  6. Exibir alunos acima de um limite de nota
  7. Exibir alunos agrupados por idade
  8. Encontrar um aluno por nome
  9. Sair da aplicação

Experimente as diferentes opções para ver como os vários mecanismos de comparação que implementamos funcionam em uma aplicação real. Por exemplo:

  • A opção 3 usa o GradeComparator para encontrar os melhores alunos por nota
  • A opção 4 usa o NameComparator para classificar os alunos por nome
  • A opção 5 usa o AgeComparator para classificar os alunos por idade

Este sistema de gerenciamento de alunos demonstra como os mecanismos de comparação que aprendemos podem ser aplicados em um cenário do mundo real para gerenciar e organizar dados de forma eficaz.

Principais Conclusões

Por meio deste laboratório, você aprendeu:

  1. Como criar uma classe Java com vários atributos
  2. Como substituir equals() e hashCode() para uma comparação de objetos adequada
  3. Como implementar a interface Comparable para definir uma ordenação natural
  4. Como usar a interface Comparator para ordenação personalizada
  5. Como aplicar esses conceitos em uma aplicação prática

Essas habilidades são fundamentais para qualquer desenvolvedor Java e serão úteis em uma ampla gama de aplicações, desde o processamento de dados até interfaces de usuário.

Resumo

Neste laboratório, você aprendeu como comparar objetos Java com base em múltiplos atributos. Você explorou três mecanismos-chave para comparação de objetos em Java:

  1. Substituindo os métodos equals() e hashCode(): Você aprendeu como implementar corretamente esses métodos para verificar se dois objetos são iguais com base em seu conteúdo.

  2. Implementando a interface Comparable: Você descobriu como definir uma ordenação natural para seus objetos implementando o método compareTo(), que permite classificar objetos com base em múltiplos atributos.

  3. Usando a interface Comparator: Você explorou como criar comparadores personalizados para classificar objetos de maneiras diferentes com base em vários critérios.

Você aplicou esses conceitos para construir um sistema prático de gerenciamento de alunos que demonstra como esses mecanismos de comparação funcionam juntos em uma aplicação do mundo real.

Essas habilidades são fundamentais para uma programação Java eficaz, especialmente ao trabalhar com coleções de objetos que precisam ser classificadas, filtradas ou comparadas. Compreender a comparação de objetos permite que você construa aplicações mais eficientes e robustas.

Continue praticando essas técnicas, pois elas serão inestimáveis em sua jornada como desenvolvedor Java.