Java Comparator Interface
Java Comparator interface is used to sort an array or List of objects based on custom sort order. The custom ordering of items is imposed by implementing Comparator’s compare() method in the objects.
1. When to Use Comparator Interface
Java Comparator interface imposes a total ordering on the objects which may not have a desired natural ordering.
For example, for a List of Employee objects, the natural order may be ordered by employee’s id. But in real-life applications, we may want to sort the list of employees by their first name, date of birth or simply any other such criteria. In such conditions, we need to use Comparator interface.
We can use the Comparator interface in the following situations.
- Sorting the array or list of objects, but NOT in natural order.
- Sorting the array or list of objects where we can not modify the source code to implement Comparable interface.
- Using group by sort on list or array of objects on multiple different fields.
2. Overriding compare() Method
To enable total ordering on objects, we need to create a class that implements the Comparator interface. Then we need to override its compare(T o1, T o2) method.
The compare() compares its two arguments for order. It returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0 .
For a given Employee class, the order by employee name can be imposed by creating a Comparator like below.
import java.util.Comparator; public class NameSorter implements Comparator < @Override public int compare(Employee e1, Employee e2) < return e1.getName().compareToIgnoreCase( e2.getName() ); >>
import java.time.LocalDate; public class Employee
3.1. Collections.sort() and Arrays.sort()
- Use Collections.sort(list, Comparator) method sort a list of objects in order imposed by provided comparator instance.
- Use Arrays.sort(array, Comparator) method sort an array of objects in order imposed by provided comparator instance.
This utility method accepts a function that extracts a sort key for the class. This is essentially a field on which the class objects will be sorted.
//Order by name Comparator.comparing(Employee::getName); //Order by name in reverse order Comparator.comparing(Employee::getName).reversed(); //Order by id field Comparator.comparing(Employee::getId); //Order by employee age Comparator.comparing(Employee::getDate);
This utility method is used for group by sort. Using this method, we can chain multiple comparators to sort the list or array of objects on multiple fields.
It is very similar to SQL GROUP BY clause to order rows on different fields.
//Order by name and then by age Comparator.comparing(Employee::getName) .thenComparing(Employee::getDob); //Order by name -> date of birth -> id Comparator.comparing(Employee::getName) .thenComparing(Employee::getDob) .thenComparing(Employee::getId);
Using the above syntax, we can create virtually any sorting logic.
This utility method returns a comparator that imposes the reverse of the natural ordering or total ordering on a collection of objects that implement the Comparable interface.
//Reverse of natural order as specified in //Comparable interface's compareTo() method Comparator.reversed(); //Reverse of order by name Comparator.comparing(Employee::getName).reversed();
3.5. Other Collection Classes
Comparators can also be used to control the order of certain data structures (such as sorted sets or sorted maps) to provide an ordering that is not natural ordering.
SortedSet sortedUniqueEmployees = new TreeSet(new NameSorter());
4. Java Comparator Examples
4.1. Sorting List of Custom Objects
Java example to sort a list of employees by name using Comparator.
ArrayList list = new ArrayList<>(); //Sort in reverse natural order Collections.sort(list, new NameSorter());
4.2. Sort List in Reverse Order
Java example to sort a list of employees by name using Comparator in reverse order.
ArrayList list = new ArrayList<>(); Collections.sort(list, Comparator.comparing( Employee::getName ).reversed());
Java example to sort a list of employees on multiple fields i.e. field by field.
ArrayList list = new ArrayList<>(); Comparator groupByComparator = Comparator.comparing(Employee::getName) .thenComparing(Employee::getDob) .thenComparing(Employee::getId); Collections.sort(list, groupByComparator);
In this tutorial, we learned about Comparator interface of Java collection framework. It helps in imposing a total order on objects without any change to the source code of that class.
We learned to sort list and array of custom objects. We also learned to reverse sort and implement group by sort in Java.
Sorting with Comparable and Comparator
Learn to sort a List of Objects by a field value. Note that if you have millions of records for sorting at a time then a database query is the best way. Otherwise, using either Comparable or Comparator interface is a very convenient approach.
In the examples given in this tutorial, we will be using the record type User. It has four fields: id , firstName , lastName and age . I have chosen these fields purposefully to show different usecases.
import java.io.Serializable; public record User(Long id, String firstName, String lastName, Integer age) implements Serializable < public User < if (age < 18) < throw new IllegalArgumentException("You cannot hire a minor person"); >> >
We will be using the given unsorted list and sorting it on different field values.
private static List getUnsortedUsers()
Moving on, we will be using the Comparable and Comparator interfaces for sorting on different field values.
2. Sorting with Comparable for Natural Ordering
2.1. Implementing Comparable Interface
Comparable interface provides a single method compareTo(T o) to implement by any class so that two objects of that class can be compared. This method is used for implementing the natural sorting behavior.
The User record after implementing the Comparable interface is as follows. The similar implementation can be done for class types as well. The default sorting has been done on the id field.
public record User(Long id, String firstName, String lastName, Integer age) implements Serializable, Comparable < public User < if (age < 18) < throw new IllegalArgumentException("You cannot hire a minor person"); >> @Override public int compareTo(User o) < return this.id.intValue() - o.id.intValue(); >>
2.2. Collections.sort() Method
We can pass the List of objects in the sort() method that will sort the objects in their natural ordering i.e. by id field.
Check out the output in the console.
[User[id=1, firstName=A, lastName=Q, age=24], User[id=2, firstName=C, lastName=O, age=27], User[id=3, firstName=D, lastName=N, age=29], User[id=4, firstName=B, lastName=P, age=22], User[id=5, firstName=E, lastName=M, age=25]]
Java Stream API has sorted() method that can sort a stream of items in the natural order. Note that stream operations do not modify the original collections, so the objects in the list will be unchanged.
List sortedList = list.stream() .sorted() .collect(Collectors.toList());
3. Sorting with Comparator for Custom Ordering
3.1. Creating Comparator Instances
Let us assume that we want to sort the users list based on some other fields, for example, by firstName or age . We can modify the User record because it already implements the natural ordering by id field.
Here comes the Comparator interface to rescue. A Comparator can be used to define the custom ordering. To sort on different object fields, we can create multiple Comparator implementations.
For example, to sort the users list by firstName , we can create FirstNameSorter class that implements the Comparator.
import java.util.Comparator; public class FirstNameSorter implements Comparator < @Override public int compare(User o1, User o2) < return o1.firstName().compareTo(o2.firstName()); >>
Note that we can use the lambda expression for creating the inline Comparator instances, for single-time uses.
Comparator firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName());
We can create group by sorting effect by combining multiple comparators using Comparator.thenComparing() method. For example, we can create a complex comparator fullNameSorter for sorting a list by first name and then by last name.
Comparator firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName()); Comparator lastNameSorter = (o1, o2) -> o1.lastName().compareTo(o2.lastName()); Comparator fullNameSorter = firstNameSorter.thenComparing(lastNameSorter);
One more type of Comparator is worth discussing that is used for reverse ordering. We can get this reverse comparator by calling reversed() method on any comparator instance.
Comparator reverseSorter = firstNameSorter.reversed();
Similar way, we can create as many comparators as needed in the applications.
To sort using Collection.sort() method, pass two method arguments. The first argument is the unsorted list and the second argument is the Comparator instance.
List list = getUnsortedUsers(); Comparator firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName()); Collections.sort(list, firstNameSorter);
To sort the stream items using comparator instance, we can pass the comparator as method argument to the sorted() method.
List list = getUnsortedUsers(); Comparator firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName()); List sortedList = list.stream() .sorted(firstNameSorter) .collect(Collectors.toList());
4. hashCode() and equals() Contract
If we have overridden equals() method in the User class, always remember to honor the contract between hashCode() and equals() methods.
If two objects are equal using equals() method then compareTo() method should return zero.
As a general practice, always use the same fields in both methods. If we are using id field in the equals() method then use the id field in compareTo() method also. An example implementation is given as follows:
import java.io.Serializable; import java.util.Objects; public record User(Long id, String firstName, String lastName, Integer age) implements Serializable, Comparable < public User < if (age < 18) < throw new IllegalArgumentException("You cannot hire a minor person"); >> @Override public int compareTo(User o) < return this.id.intValue() - o.id.intValue(); >@Override public int hashCode() < return Objects.hash(id); >@Override public boolean equals(Object obj) < if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; User other = (User) obj; return Objects.equals(id, other.id); >>
In this Java Comparable and Comparator tutorial, we learned to implement both interfaces in different ways for different usecases. We also saw the use of both interfaces in Java Stream API.
Finally, we understood how to correctly override hashCode() and equals() method on objects to keep sorting functioning properly.