In Java, sorting objects is a common task, especially when dealing with collections of custom objects. The Comparator
interface in Java is a powerful tool that allows you to define custom sorting logic for objects of user-defined classes. This guide will explore the Comparator
interface with practical examples, demonstrating how to sort objects based on different criteria.
The Comparator
interface is part of the java.util
package and provides a way to compare two objects of the same class. This is particularly useful when the natural ordering of objects (defined by Comparable
) is not sufficient, or when you need to sort objects in different ways.
Understanding the Comparator Interface
The core of the Comparator
interface lies in its compare(Object obj1, Object obj2)
method. This method dictates how two objects, obj1
and obj2
, are compared for sorting purposes.
Syntax:
public int compare(Object obj1, Object obj2);
The compare()
method returns an integer value based on the comparison between obj1
and obj2
:
- Negative integer: If
obj1
is less thanobj2
. - Zero: If
obj1
is equal toobj2
. - Positive integer: If
obj1
is greater thanobj2
.
This return value is crucial for sorting algorithms like Collections.sort()
to determine the order of objects in a collection.
Methods to Implement Comparator
There are a couple of primary ways to implement the Comparator
interface to sort objects in Java. Let’s explore these methods with examples.
Method 1: Implementing Comparator using a Separate Class
One common approach is to create a separate class that implements the Comparator
interface. This class will contain the logic for comparing objects of your custom class.
Example: Sorting Student Objects by Roll Number
Let’s consider a Student
class with rollno
and name
as attributes. We want to sort a list of Student
objects based on their roll numbers.
// Define the Student class
class Student {
int rollno;
String name;
// Constructor
Student(int rollno, String name) {
this.rollno = rollno;
this.name = name;
}
// Method to print Student details
@Override
public String toString() {
return rollno + ": " + name;
}
}
// Helper class implementing Comparator interface
class SortbyRoll implements Comparator<Student> {
// Compare by roll number in ascending order
public int compare(Student a, Student b) {
return a.rollno - b.rollno;
}
}
public class GFG {
public static void main(String[] args) {
// List of Students
List<Student> students = new ArrayList<>();
// Add Elements in List
students.add(new Student(111, "Mayank"));
students.add(new Student(131, "Anshul"));
students.add(new Student(121, "Solanki"));
students.add(new Student(101, "Aggarwal"));
// Sort students by roll number
// using SortbyRoll comparator
Collections.sort(students, new SortbyRoll());
System.out.println("Sorted by Roll Number");
// Iterating over entries to print them
for (Student student : students) {
System.out.println(student);
}
}
}
Output:
Sorted by Roll Number
101: Aggarwal
111: Mayank
121: Solanki
131: Anshul
In this example, SortbyRoll
is a separate class that implements Comparator<Student>
. The compare()
method within SortbyRoll
compares the rollno
of two Student
objects. Collections.sort(students, new SortbyRoll())
then uses this comparator to sort the students
list.
Method 2: Implementing Comparator using Lambda Expressions
Java 8 introduced lambda expressions, providing a more concise way to implement functional interfaces like Comparator
. Lambda expressions can simplify comparator implementation, especially for simple comparison logic.
Example: Sorting Student Objects by Roll Number using Lambda
We can achieve the same sorting by roll number using a lambda expression directly within the sort()
method.
// Define the Student class (same as above)
class Student {
int rollno;
String name;
// Constructor and toString() method (same as above)
Student(int rollno, String name) {
this.rollno = rollno;
this.name = name;
}
@Override
public String toString() {
return rollno + ": " + name;
}
}
public class LambdaComparatorExample {
public static void main(String[] args) {
// List of Students
List<Student> students = new ArrayList<>();
// Add Elements in List (same as above)
students.add(new Student(111, "Mayank"));
students.add(new Student(131, "Anshul"));
students.add(new Student(121, "Solanki"));
students.add(new Student(101, "Aggarwal"));
// Sort students by roll number using lambda
students.sort((Student a, Student b) -> a.rollno - b.rollno);
System.out.println("Sorted by Roll Number (Lambda)");
// Iterating over entries to print them
for (Student student : students) {
System.out.println(student);
}
}
}
Output:
Sorted by Roll Number (Lambda)
101: Aggarwal
111: Mayank
121: Solanki
131: Anshul
Here, students.sort((Student a, Student b) -> a.rollno - b.rollno)
directly provides the comparison logic as a lambda expression. This approach is more compact and often preferred for simple comparators.
Sorting Collection by More Than One Field
The power of Comparator
truly shines when you need to sort collections based on multiple fields. You can define a comparator that considers multiple attributes in a specific order to determine the object ordering.
Example: Sorting Student Objects by Name then Age
Let’s extend our Student
class to include name
and age
. We want to sort students primarily by name (alphabetically) and then by age for students with the same name.
// Define the Student class
class Student {
String name;
Integer age;
// Constructor
Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
// Method to print student details
@Override
public String toString() {
return name + " : " + age;
}
}
// Comparator in a Helper Class
class CustomerSortingComparator implements Comparator<Student> {
// Compare first by name, then by age
public int compare(Student student1, Student student2) {
// Compare by name first
int nameCompare = student1.getName().compareTo(student2.getName());
// If names are the same, compare by age
int ageCompare = student1.getAge().compareTo(student2.getAge());
// Return the result: first by name, second by age
return (nameCompare == 0) ? ageCompare : nameCompare;
}
}
public class ComparatorHelperClassExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Ajay", 27));
students.add(new Student("Sneha", 23));
students.add(new Student("Simran", 37));
students.add(new Student("Ankit", 22));
students.add(new Student("Anshul", 29));
students.add(new Student("Sneha", 22));
// Original List
System.out.println("Original List");
for (Student it : students) {
System.out.println(it);
}
System.out.println();
// Sort students by name, then by age
// using the CustomerSortingComparator
Collections.sort(students, new CustomerSortingComparator());
// Display message only
System.out.println("After Sorting");
// Iterating using enhanced for-loop
// after Sorting ArrayList
for (Student it : students) {
System.out.println(it);
}
}
}
Output:
Original List
Ajay : 27
Sneha : 23
Simran : 37
Ankit : 22
Anshul : 29
Sneha : 22
After Sorting
Ajay : 27
Ankit : 22
Anshul : 29
Simran : 37
Sneha : 22
Sneha : 23
In CustomerSortingComparator
, we first compare students by name using compareTo()
. If the names are the same (nameCompare == 0
), we then compare them by age. This ensures a secondary sorting criterion.
Alternative Method: Chaining Comparators with thenComparing()
Java 8 and later versions offer a more elegant way to handle multi-field sorting using the thenComparing()
method of the Comparator
interface. This allows you to chain comparators for different fields in a readable and concise manner.
Example: Sorting Student Objects by Name then Age using thenComparing()
// Define the Student class (same as above)
class Student {
String name;
Integer age;
// Constructor, getName(), getAge(), and toString() methods (same as above)
Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
@Override
public String toString() {
return name + " : " + age;
}
}
public class ComparatorThenComparingExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Ajay", 27));
students.add(new Student("Sneha", 23));
students.add(new Student("Simran", 37));
students.add(new Student("Ankit", 22));
students.add(new Student("Anshul", 29));
students.add(new Student("Sneha", 22));
// Original List
System.out.println("Original List:");
for (Student it : students) {
System.out.println(it);
}
System.out.println();
// Sort students by name, then by age
students.sort(Comparator.comparing(Student::getName).thenComparing(Student::getAge));
// Display message after sorting
System.out.println("After Sorting:");
for (Student it : students) {
System.out.println(it);
}
}
}
Output:
Original List:
Ajay : 27
Sneha : 23
Simran : 37
Ankit : 22
Anshul : 29
Sneha : 22
After Sorting:
Ajay : 27
Ankit : 22
Anshul : 29
Simran : 37
Sneha : 22
Sneha : 23
Comparator.comparing(Student::getName).thenComparing(Student::getAge)
creates a comparator that first compares by name using comparing(Student::getName)
and then, for elements with the same name, uses thenComparing(Student::getAge)
to compare by age. This method is clean, readable, and leverages method references for conciseness.
Comparator vs Comparable
While both Comparator
and Comparable
are used for sorting in Java, they serve different purposes and have key distinctions.
Feature | Comparator | Comparable |
---|---|---|
Sorting Logic Location | Defined externally (separate class or lambda) | Defined within the class itself |
Multiple Sorting Orders | Supported (easily create different comparators) | Not directly supported (single natural order) |
Interface Methods | compare(obj1, obj2) |
compareTo(anotherObject) |
Functional Interface | Yes | No |
Usage | Flexible, reusable, for external sorting | Simple, for defining natural ordering |
In essence, use Comparable
when you want to define a natural, default sorting order for your class. Use Comparator
when you need more flexibility, want to sort based on different criteria, or when you can’t modify the class itself (e.g., sorting objects from a library).
Conclusion
The Java Comparator
interface is an essential tool for sorting objects in Java. It provides the flexibility to define custom sorting logic based on various criteria, making it invaluable for complex sorting requirements. Whether you choose to implement comparators using separate classes or concise lambda expressions, understanding Comparator
is crucial for effective object sorting in Java applications.