¿Cómo comparar objetos Java basados en múltiples atributos?

JavaBeginner
Practicar Ahora

Introducción

Comparar objetos Java basándose en múltiples atributos es una habilidad esencial para cualquier desarrollador Java. Esta capacidad permite la clasificación, el filtrado y la organización eficientes de datos en las aplicaciones. En este laboratorio, aprenderá a implementar diferentes mecanismos de comparación en Java para manejar objetos con múltiples propiedades.

Exploraremos los enfoques fundamentales para comparar objetos en Java, incluyendo la sobreescritura del método equals(), la implementación de la interfaz Comparable y el uso de la interfaz Comparator. A través de ejemplos prácticos y ejercicios prácticos, obtendrá una sólida comprensión de cuándo y cómo usar cada enfoque.

Creación de una Clase Estudiante con Múltiples Atributos

En este primer paso, crearemos una clase Java simple para representar a un estudiante con múltiples atributos. Esto servirá como base para aprender sobre la comparación de objetos en Java.

Configuración de la Estructura del Proyecto

Comencemos creando un directorio para nuestro proyecto y navegando a él:

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

Creación de la Clase Estudiante

Ahora, creemos una clase Student con múltiples atributos como nombre, edad y calificación. Abra el WebIDE y cree un nuevo archivo llamado Student.java en el directorio del proyecto con el siguiente contenido:

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 clase representa a un estudiante con tres atributos: name (un String), age (un entero) y grade (un double). También hemos incluido un constructor, getters para cada atributo y un método toString() para facilitar la visualización de la información del estudiante.

Prueba de la Clase Estudiante

Creemos una clase principal para probar nuestra clase Student. Cree un nuevo archivo llamado StudentTest.java con el siguiente contenido:

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.");
    }
}

Compilación y Ejecución

Ahora, compilemos y ejecutemos nuestro código:

javac StudentTest.java
java StudentTest

Debería ver una salida similar 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.

Comprensión de los Conceptos Básicos de la Comparación de Objetos

Observe en nuestra prueba que usamos el operador == para comparar dos objetos Student. Esto se llama comparación de identidad (identity comparison) en Java, que verifica si dos referencias apuntan al mismo objeto en la memoria.

Sin embargo, a menudo necesitamos comparar objetos basándonos en su contenido o atributos, lo que se llama comparación de igualdad (equality comparison). Java proporciona varios mecanismos para este propósito:

  1. Sobreescribir el método equals()
  2. Implementar la interfaz Comparable
  3. Usar la interfaz Comparator

En los siguientes pasos, implementaremos estos mecanismos para comparar objetos Student basándonos en sus atributos.

Implementación de los métodos equals() y hashCode()

Antes de sumergirnos en las interfaces Comparable y Comparator, implementemos primero los métodos equals() y hashCode() en nuestra clase Student. Estos métodos son fundamentales para la correcta comparación de objetos en Java.

Comprensión de equals() y hashCode()

En Java, el método equals() se utiliza para verificar si dos objetos son iguales basándose en su contenido, mientras que el método hashCode() genera un valor numérico que representa el objeto. Estos dos métodos funcionan juntos, especialmente cuando los objetos se almacenan en colecciones como HashMap o HashSet.

Una implementación correcta debe seguir estas reglas:

  • Si dos objetos son iguales según equals(), deben tener el mismo código hash.
  • Si dos objetos tienen el mismo código hash, no son necesariamente iguales.

Actualización de la Clase Estudiante

Actualicemos nuestra clase Student para sobreescribir estos métodos. Abra Student.java y agregue los siguientes 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;
    }
}

El método equals() sigue un patrón estándar:

  1. Verificar si el objeto se está comparando consigo mismo
  2. Verificar si el objeto es nulo o de una clase diferente
  3. Convertir el objeto al tipo apropiado
  4. Comparar cada atributo para determinar la igualdad

El método hashCode() combina los códigos hash de todos los atributos para generar un código hash único para el objeto.

Prueba de equals() y hashCode()

Ahora, actualicemos nuestro archivo StudentTest.java para probar estos 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());
    }
}

Compilación y Ejecución

Compilemos y ejecutemos nuestro código actualizado:

javac Student.java StudentTest.java
java StudentTest

La salida debería ser similar 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, aunque alice y aliceDuplicate son objetos diferentes (como se muestra en la comparación ==), se consideran iguales según nuestro método equals() porque tienen los mismos valores de atributo. Además, tienen el mismo código hash, como lo exige el contrato entre equals() y hashCode().

Si bien equals() es útil para determinar si dos objetos son iguales, no nos ayuda a establecer un orden entre los objetos (como cuál debería ir primero en una lista ordenada). Para eso, necesitamos la interfaz Comparable, que implementaremos en el siguiente paso.

Implementación de la Interfaz Comparable

El método equals() nos permite verificar si dos objetos son iguales, pero no nos ayuda a establecer un orden. Para eso, Java proporciona la interfaz Comparable, que nos permite definir un "orden natural" para nuestros objetos.

Comprensión de la Interfaz Comparable

La interfaz Comparable<T> tiene un único método, compareTo(T o), que compara el objeto actual con otro objeto del mismo tipo. El método devuelve:

  • Un entero negativo si el objeto actual es menor que el otro objeto
  • Cero si el objeto actual es igual al otro objeto
  • Un entero positivo si el objeto actual es mayor que el otro objeto

Al implementar esta interfaz, podemos definir cómo se deben ordenar los objetos de nuestra clase.

Actualización de la Clase Estudiante

Actualicemos nuestra clase Student para implementar la interfaz Comparable. Definiremos el orden natural basándonos en la calificación del estudiante (las calificaciones más altas van primero), luego por edad (los estudiantes más jóvenes van primero) y, finalmente, por nombre (orden alfabético).

Actualice su archivo 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);
    }
}

Creación de una Prueba para la Implementación de Comparable

Creemos un nuevo archivo ComparableTest.java para probar nuestra implementación de 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");
        }
    }
}

Compilación y Ejecución

Compilemos y ejecutemos nuestro código:

javac Student.java ComparableTest.java
java ComparableTest

La salida debería ser similar 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 los estudiantes ahora están ordenados por su calificación en orden descendente. Cuando las calificaciones son iguales (como con Alice y David, ambos con 85.5), se ordenan por edad en orden ascendente.

La interfaz Comparable proporciona un orden natural para nuestros objetos, pero ¿qué pasa si queremos ordenar los objetos de diferentes maneras en diferentes momentos? Por ejemplo, a veces podríamos querer ordenar a los estudiantes por nombre y otras veces por edad. Ahí es donde entra en juego la interfaz Comparator, que exploraremos en el siguiente paso.

Uso de Comparator para Ordenamiento Personalizado

La interfaz Comparable proporciona un orden natural para nuestra clase, pero a veces necesitamos ordenar objetos de diferentes maneras según diferentes criterios. Aquí es donde la interfaz Comparator resulta útil.

Comprensión de la Interfaz Comparator

La interfaz Comparator<T> define un método compare(T o1, T o2) que compara dos objetos del mismo tipo. A diferencia de Comparable, que es implementada por la clase que se está comparando, un Comparator es una clase separada o una expresión lambda que puede definir varios criterios de ordenamiento.

Creación de Comparadores Personalizados

Creemos varios comparadores para nuestra clase Student:

  1. NameComparator: Ordena a los estudiantes por nombre (alfabéticamente)
  2. AgeComparator: Ordena a los estudiantes por edad (ascendente)
  3. GradeComparator: Ordena a los estudiantes por calificación (descendente)

Cree un nuevo archivo llamado StudentComparators.java con el siguiente contenido:

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());
        }
    }
}

Prueba de los Comparadores

Ahora, creemos una clase de prueba para demostrar cómo usar estos comparadores. Cree un archivo llamado 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);
        }
    }
}

Compilación y Ejecución

Compilemos y ejecutemos nuestro código:

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

La salida demostrará cómo los diferentes comparadores afectan el orden de clasificación de los estudiantes:

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)

Diferencias Clave entre Comparable y Comparator

Comprender las diferencias entre Comparable y Comparator es importante:

  1. Ubicación de la implementación:

    • Comparable es implementado por la propia clase.
    • Comparator se implementa en una clase separada o como una expresión lambda.
  2. Número de ordenamientos:

    • Comparable define un único "orden natural" para una clase.
    • Comparator permite múltiples ordenamientos diferentes.
  3. Firmas de los métodos:

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

    • Comparable es más simple cuando hay un orden natural obvio.
    • Comparator es más flexible cuando se necesitan múltiples ordenamientos.

En el paso final, crearemos una aplicación del mundo real que utiliza tanto Comparable como Comparator para administrar una base de datos de estudiantes.

Construyendo un Sistema de Gestión de Estudiantes

Ahora que entendemos cómo comparar objetos usando equals(), Comparable y Comparator, construyamos un sistema simple de gestión de estudiantes que combine todo en una aplicación práctica.

Creación de la Clase StudentManager

Creemos una clase que gestionará una colección de estudiantes y proporcionará varias operaciones sobre ellos. Cree un archivo llamado 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();
    }
}

Creación de la Aplicación Principal

Ahora, creemos nuestra aplicación principal que utiliza la clase StudentManager. Cree un archivo llamado 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.");
            }
        }
    }
}

Compilación y Ejecución de la Aplicación

Compilemos y ejecutemos nuestro sistema de gestión de estudiantes:

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

Ahora verá un menú interactivo que le permite:

  1. Agregar nuevos estudiantes
  2. Mostrar todos los estudiantes
  3. Mostrar los mejores estudiantes por calificación
  4. Mostrar los estudiantes ordenados por nombre
  5. Mostrar los estudiantes ordenados por edad
  6. Mostrar los estudiantes por encima de un umbral de calificación
  7. Mostrar los estudiantes agrupados por edad
  8. Encontrar un estudiante por nombre
  9. Salir de la aplicación

Pruebe las diferentes opciones para ver cómo funcionan los diversos mecanismos de comparación que hemos implementado en una aplicación real. Por ejemplo:

  • La opción 3 utiliza el GradeComparator para encontrar los mejores estudiantes por calificación
  • La opción 4 utiliza el NameComparator para ordenar a los estudiantes por nombre
  • La opción 5 utiliza el AgeComparator para ordenar a los estudiantes por edad

Este sistema de gestión de estudiantes demuestra cómo los mecanismos de comparación que hemos aprendido se pueden aplicar en un escenario del mundo real para gestionar y organizar datos de manera efectiva.

Conclusiones Clave

A través de este laboratorio, ha aprendido:

  1. Cómo crear una clase Java con múltiples atributos
  2. Cómo anular equals() y hashCode() para una comparación de objetos adecuada
  3. Cómo implementar la interfaz Comparable para definir un orden natural
  4. Cómo usar la interfaz Comparator para un ordenamiento personalizado
  5. Cómo aplicar estos conceptos en una aplicación práctica

Estas habilidades son fundamentales para cualquier desarrollador de Java y serán útiles en una amplia gama de aplicaciones, desde el procesamiento de datos hasta las interfaces de usuario.

Resumen

En este laboratorio, ha aprendido a comparar objetos Java basados en múltiples atributos. Ha explorado tres mecanismos clave para la comparación de objetos en Java:

  1. Anulación de los métodos equals() y hashCode(): Aprendió a implementar correctamente estos métodos para verificar si dos objetos son iguales en función de su contenido.

  2. Implementación de la interfaz Comparable: Descubrió cómo definir un orden natural para sus objetos implementando el método compareTo(), lo que le permite ordenar objetos basados en múltiples atributos.

  3. Uso de la interfaz Comparator: Exploró cómo crear comparadores personalizados para ordenar objetos de diferentes maneras según varios criterios.

Ha aplicado estos conceptos para construir un sistema práctico de gestión de estudiantes que demuestra cómo estos mecanismos de comparación funcionan juntos en una aplicación del mundo real.

Estas habilidades son fundamentales para una programación Java efectiva, especialmente cuando se trabaja con colecciones de objetos que necesitan ser ordenados, filtrados o comparados. Comprender la comparación de objetos le permite construir aplicaciones más eficientes y robustas.

Siga practicando estas técnicas, ya que serán invaluables en su viaje como desarrollador Java.