Do You Know – Comparator in Java 7 had just 2 methods –
equals(). The enhanced Comparator in Java 8 now boasts of 19 methods. Yes, 17 more methods! What’s more Comparator now is an official Functional Interface as well!
Comparator has undergone a major overhaul in Java 8 while still retaining its essence which is to compare and sort objects in Collections. Comparator now supports declarations via lambda expressionsRead Lambda Expressions Tutorial as it is a Functional Interface. Comparator has a new method
comparing() which uses an instance of
java.util.function.Function functional interface, specified using lambda expression or its equivalent method reference, for
Comparator instance creation. In addition, multiple sort criteria can now be clubbed using
comparing() with a
thenComparing() method. The range of new capabilities is rounded off with methods for using natural comparison order, in-built Null handling and sort order reversal.
In this tutorial, we will first take a quick look at how Comparators were used before Java 8. We will then take an in-depth look at the new Comparator aspects mentioned above to see how
java.util.Comparator has evolved into an enhanced comparison and ordering utility in Java 8.
How Comparator was used prior to Java 8
Until Java 7,
Comparator interface could only be used in one single way. Given a collection of objects of type
<T> to sort, one would create an implementation of
Comparator<T> interface, override the
compare() method of the interface with the desired comparison logic and use
Collections.sort() or similar such methods in Collections API to sort the collection of objects.
Let us now see an example of how Comparators were used prior to Java 8. Let us first create a Class
Employee which will be the type of object we will be sorting across all our examples for Java 7 and Java 8 Comparators-
Next we will see how Comparator was used to sort collections prior to Java 8 –
Comparatorinteface and overrides the
compare()method to compare
Employeeobjects passed to it based on the natural ordering of their names of
ComparatorOldWayclass sorts a static list of
Employeeobjects using an instance of
- Output shows that the
employeeListgets sorted alphabetically based on the names of the employees.
Java 8’s Comparator is a Functional Interface
Owing to the fact that the Comparator interface has just one abstract method,
compare(), it automatically qualifies to be a Functional Interface
Click to read detailed Article on Functional Interfaces in Java 8. Nevertheless, Java 8 designers have gone ahead and annotated the
Comparator class interface
@FunctionalInterface to enforce its role as a Functional Interface. Being a functional interface, Comparator can now be used as an assignment target for a lambda expression or a method reference.
Java 8’s Comparator as an assignment target for LambdaExpressions
Given the fact that its a functional interface, an instance of a Comparator can now be created in Java 8 with a lambda expression specifying its comparison logic. Take a look at the code snippet below –
ComparatorsInJava8class uses the same list of
Employeeobjects as that used for the previous example for Java 7 style of
- An instance of the
empNameComparator, is created using a lambda expression.
- The lambda expression takes 2
emp2, as input and outputs the comparison of their names using the natural comparison order of Strings.
empNameComparatorfor sorting results in a correctly sorted
Employeelist by name.
Java 8 Comparator’s comparing() method’s working
comparing() method is a new static method introduced in Comparators in Java 8. It has the signature –
static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)
comparing() method works by taking a
Function<T,R> functional interface instance as an input, where
T is the type of input object and
R is the sort key which is returned (or extracted) from the input object when
Function<T,R> processes it.
Let’s see a code snippet to understand the use of
comparing() method. I have removed the code which was same as previous example below for brevity –
- An instance of the
comparatorObj, is created using the
comparing()is passed a lambda expression, which corresponds to a
Function<T,R>instance accepting an
Employeeobject as input and returns an employee name – the sort key.
comparatorObjfor sorting results in a correctly sorted
Employeelist by name.
- NOTE – Instead of the lambda expression, you can also use an equivalent method reference as well. The
comparing()method with a method reference will then be written like this –
Java 8 Comparator’s thenComparing() method for multiple sort criteria
Many-a-times we need to sort with multiple sort orders. I.e. on more than one attributes of an object. The second level sort order gets used if the first level sort criteria is indecisive. In the list of Employees we are using as an example, there are two employees with the name Harry Major. Let us take a second sort order of age and say that in such cases we will put the employee with the younger age first.
For exactly these kinds of multiple sort ordering, Java 8 Comparator provides a default method
thenComparing() which has the signature –
default Comparator<T> thenComparing(Comparator<? super T> other)
thenComparing() method then does the second level sort, if the first level sort is indecisive. Let us extend the above code example to add a second-level sort criteria by age.
comparing()method is invoked with method reference for
getName()method. This returns a
Comparatorinstance with the first level sort based on
Employeename as we saw in previous section.
- We append
Comparatorinstance returned using
comparing()method, which adds a second level sort based on
- The output is as expected with the employee named ‘Harry Major’ with the lesser age placed earlier in the sorted
employeeListthan his elder namesake employee.
Java 8 Comparator’s natural order comparison methods
Java 8 Comparator supports natural order comparison of elements in a Collection. I.e. instead of defining our own comparison logic, we can instead use the inherent natural order defined for that type of element via its implementation of
Comparable interface. Comparator provides two static methods
reverseOrder() to allow natural order comparison and reverse natural order comparison respectively. These have the syntax –
static <T extends Comparable<? super T>> Comparator<T> naturalOrder()
static <T extends Comparable<? super T>> Comparator<T> reverseOrder()
Let us take the case of String type which has natural comparison order defined as alphabetical. To use our existing example for sorting using Employee objects, we will extract out all the names of Employees by utilizing the
stream() method to convert the Employee list into a Stream of Employee objects. We will then map these Employee objects using a Function into their names and collect these names into a list of Strings holding these names.
(In case you are not aware of mapping of elements in a Stream using map() method you can read the tutorial on Stream.map() method
Tutorial on how Stream API’s map() method works, and tutorial on Function interface
Read java.util.function.Function interface tutorial.)
- Employee names are extracted into an
empNames Listas explained before the code snippet.
- empNames List is then sorted using the
Comparator.naturalOrder()method which returns a
String’s natural comparison order based on the
empName’s generic type of
- Employee names are sorted in natural comparison order, i.e. alphabetical order, and printed.
- Instead of invoking the
Comparator.naturalOrder()method to the
empNames.sort()method, you can invoke the
Comparator.reverseOrder()method in order to sort the
empNames Listin reverse of natural comparison order or reverse alphabetical order.
Java 8 Comparator’s null handling using nullsFirst()and nullsLast() methods
There are instances where the sort key value is nullable. We have to then decide whether we want to place objects with sort key as null earlier in the ordering than the objects with non-null sort key or at the end of the list after the objects with non-null sort key. Java 8 Comparators provide
nullsLast() static methods for exactly such sort order handling of null valued sort keys. These methods have the signature –
static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)
static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)
To understand the functioning of these methods, let us make the two of the employee names as null. The below two sections of code and respective output shows how the use of
nullsLast() handles sorting when some objects have sort-key as null.
OUTPUT of the above code Quick Explanation of the above code-
nullsFirst(), the comparator places the two
Employeeobjects with the sort key null (employee name null) before the other Employee objects in the list.
nullsLast(), the comparator places these two
Employeeobjects after the other Employee objects in the list.
Java 8 Comparator’s sort order reversal method – reversed()
In case you simply want to sort in the order opposite to a defined comparator’s sorting order then you need not write the reverse comparison logic again. Instead simply use the
Comparator.reversed() default method.
reversed() method has the signature –
default Comparator<T> reversed()
- Comparator’s default method
reversed()is applied to the
empNameComparatorwhich has been initially defined to sort in alphabetical order of
- Due to
reversed()being applied to it, the
Employeeobjects are printed in reverse alphabetical order of their names.
Summary– In this tutorial on Java 8 Comparators we first had a look at how Comparators were used till Java 7. Next we learnt that
Comparator is now a functional interface and how it can be defined using a lambda expression. We then had a look at the
comparing() method which allows us to define a Comparator using a Function instance which returns the sort key. Next we looked at
thenComparing() method which allows us to sort using multiple sort orders. This was followed by understanding the natural order comparison related methods of Comparator –
reverseOrder(). Next we looked at the null-valued sort key handling methods –
nullsLast(). Lastly, we understood the
reversed() method which allows us to sort in the reverse order of a defined