Использование Comparator для пользовательского упорядочивания
Интерфейс Comparable обеспечивает естественный порядок для нашего класса, но иногда нам нужно сортировать объекты разными способами в зависимости от разных критериев. Именно здесь пригодится интерфейс Comparator.
Понимание интерфейса Comparator
Интерфейс Comparator<T> определяет метод compare(T o1, T o2), который сравнивает два объекта одного и того же типа. В отличие от Comparable, который реализуется самим классом, который сравнивается, Comparator — это отдельный класс или лямбда-выражение, которое может определять различные критерии упорядочивания.
Создание пользовательских компараторов
Давайте создадим несколько компараторов для нашего класса Student:
NameComparator: Сортирует студентов по имени (в алфавитном порядке)
AgeComparator: Сортирует студентов по возрасту (по возрастанию)
GradeComparator: Сортирует студентов по оценке (по убыванию)
Создайте новый файл с именем StudentComparators.java со следующим содержимым:
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());
}
}
}
Тестирование компараторов
Теперь давайте создадим тестовый класс, чтобы продемонстрировать, как использовать эти компараторы. Создайте файл с именем 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);
}
}
}
Компиляция и запуск
Давайте скомпилируем и запустим наш код:
javac Student.java StudentComparators.java ComparatorTest.java
java ComparatorTest
Вывод покажет, как разные компараторы влияют на порядок сортировки студентов:
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)
Ключевые различия между Comparable и Comparator
Понимание различий между Comparable и Comparator важно:
-
Место реализации:
Comparable реализуется самим классом.
Comparator реализуется в отдельном классе или как лямбда-выражение.
-
Количество упорядочений:
Comparable определяет одно «естественное упорядочение» для класса.
Comparator допускает несколько разных упорядочений.
-
Сигнатуры методов:
Comparable имеет int compareTo(T o)
Comparator имеет int compare(T o1, T o2)
-
Использование:
Comparable проще, когда есть очевидный естественный порядок.
Comparator более гибкий, когда требуется несколько упорядочений.
На последнем шаге мы создадим реальное приложение, которое использует как Comparable, так и Comparator для управления базой данных студентов.