Using Comparator for Custom Ordering
The Comparable
interface provides a natural ordering for our class, but sometimes we need to sort objects in different ways based on different criteria. This is where the Comparator
interface comes in handy.
Understanding the Comparator Interface
The Comparator<T>
interface defines a method compare(T o1, T o2)
that compares two objects of the same type. Unlike Comparable
, which is implemented by the class being compared, a Comparator
is a separate class or lambda expression that can define various ordering criteria.
Creating Custom Comparators
Let's create several comparators for our Student
class:
NameComparator
: Sorts students by name (alphabetically)
AgeComparator
: Sorts students by age (ascending)
GradeComparator
: Sorts students by grade (descending)
Create a new file called StudentComparators.java
with the following content:
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());
}
}
}
Testing the Comparators
Now let's create a test class to demonstrate how to use these comparators. Create a file named 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);
}
}
}
Compile and Run
Let's compile and run our code:
javac Student.java StudentComparators.java ComparatorTest.java
java ComparatorTest
The output will demonstrate how the different comparators affect the sorting order of the students:
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)
Key Differences Between Comparable and Comparator
Understanding the differences between Comparable
and Comparator
is important:
-
Implementation location:
Comparable
is implemented by the class itself.
Comparator
is implemented in a separate class or as a lambda expression.
-
Number of orderings:
Comparable
defines a single "natural ordering" for a class.
Comparator
allows multiple different orderings.
-
Method signatures:
Comparable
has int compareTo(T o)
Comparator
has int compare(T o1, T o2)
-
Usage:
Comparable
is simpler when there's an obvious natural ordering.
Comparator
is more flexible when multiple orderings are needed.
In the final step, we'll create a real-world application that uses both Comparable
and Comparator
for managing a student database.