The Complete Java 8 Comparator Tutorial with examples
Do You Know - Comparator in Java 7 had just 2 methods -
The
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
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
[su_box title="Java 8 Comparator order reversal using reversed() method" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]public static void main(String[] args) {
Comparator<Employee> empNameComparator = Comparator.comparing(Employee::getName).reversed();
Collections.sort(employeeList, empNameComparator);
employeeList.forEach(System.out::println);
}[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"]Employee Name:Tom Jones Age:45
Employee Name:Nancy Smith Age:15
Employee Name:Harry Major Age:35
Employee Name:Harry Major Age:25
Employee Name:Ethan Hardy Age:65
Employee Name:Deborah Sprightly Age:29
[/su_note]
Quick Explanation of the above code-
compare()
and 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-
[su_box title="Employee.java" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]
//Employee.java
package com.javabrahman.java8;
public class Employee{
private String name;
private Integer age;
public Employee(String name, Integer age){
this.name=name;
this.age=age;
}
//--getters/setters for name and age go here
public String toString(){
return "Employee Name:"+this.name
+" Age:"+this.age;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Employee)) {
return false;
}
Employee empObj = (Employee) obj;
return this.age==empObj.age
&& this.name.equalsIgnoreCase(empObj.name);
}
@Override
public int hashCode() {
int hash = 1;
hash = hash * 17 + this.name.hashCode();
hash = hash * 31 + this.age;
return hash;
}
}[/java][/su_box]
Next we will see how Comparator was used to sort collections prior to Java 8 -
[su_box title="How Comparator was used prior to Java 8" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]package com.javabrahman.java8.comparator;
import java.util.Comparator;
import com.javabrahman.java8.Employee;
public class EmployeeComparator implements Comparator<Employee> {
@Override
public int compare(Employee emp1, Employee emp2) {
return (emp1.getName().compareTo(emp2.getName()));
}
}
//ComparatorOldWay.java
package com.javabrahman.java8.comparator;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.javabrahman.java8.Employee;
public class ComparatorOldWay {
static List<Employee> employeeList =
Arrays.asList(new Employee("Tom Jones", 45),
new Employee("Harry Major", 35),
new Employee("Harry Major", 25),
new Employee("Ethan Hardy", 65),
new Employee("Nancy Smith", 15),
new Employee("Deborah Sprightly", 29));
public static void main(String args[]) {
Collections.sort(employeeList, new EmployeeComparator());
employeeList.forEach(System.out::println);
}
}[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"]Employee Name:Deborah Sprightly Age:29
Employee Name:Ethan Hardy Age:65
Employee Name:Harry Major Age:35
Employee Name:Harry Major Age:25
Employee Name:Nancy Smith Age:15
Employee Name:Tom Jones Age:45
[/su_note]
Quick Explanation of the above code-
EmployeeComparator
class implementsComparator
inteface and overrides thecompare()
method to compareEmployee
objects passed to it based on the natural ordering of their names ofString
type.ComparatorOldWay
class sorts a static list ofEmployee
objects using an instance ofEmployeeComparator
and theCollections.sort()
method.- Output shows that the
employeeList
gets sorted alphabetically based on the names of the employees.
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 -
[su_box title="Defining a Comparator using lambda expression" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]package com.javabrahman.java8.comparator;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.javabrahman.java8.Employee;
public class ComparatorsInJava8 {
static List<Employee> employeeList =
Arrays.asList(new Employee("Tom Jones", 45),
new Employee("Harry Major", 35),
new Employee("Harry Major", 25),
new Employee("Ethan Hardy", 65),
new Employee("Nancy Smith", 15),
new Employee("Deborah Sprightly", 29));
public static void main(String[] args) {
Comparator<Employee> empNameComparator = (Employee emp1, Employee emp2) -> {
return (emp1.getName().compareTo(emp2.getName()));
};
Collections.sort(employeeList, empNameComparator);
employeeList.forEach(System.out::println);
}
}[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"]Employee Name:Deborah Sprightly Age:29
Employee Name:Ethan Hardy Age:65
Employee Name:Harry Major Age:35
Employee Name:Harry Major Age:25
Employee Name:Nancy Smith Age:15
Employee Name:Tom Jones Age:45
[/su_note]
Quick Explanation of the above code-
ComparatorsInJava8
class uses the same list ofEmployee
objects as that used for the previous example for Java 7 style ofComparator
.- An instance of the
Comparator
,empNameComparator
, is created using a lambda expression. - The lambda expression takes 2
Employee
instances,emp1
andemp2
, as input and outputs the comparison of their names using the natural comparison order of Strings. - Using
empNameComparator
for sorting results in a correctly sortedEmployee
list by name.
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 -
[su_box title="Comparator creation using static method comparing()" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]public static void main(String[] args) {
Comparator<Employee> comparatorObj=Comparator.comparing(Employee emp -> emp.getName());
Collections.sort(employeeList, comparatorObj);
employeeList.forEach(System.out::println);
}[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"]Employee Name:Deborah Sprightly Age:29
Employee Name:Ethan Hardy Age:65
Employee Name:Harry Major Age:35
Employee Name:Harry Major Age:25
Employee Name:Nancy Smith Age:15
Employee Name:Tom Jones Age:45
[/su_note]
Quick Explanation of the above code-
- An instance of the
Comparator
,comparatorObj
, is created using thestatic
methodComparator.comparing()
. - The
comparing()
is passed a lambda expression, which corresponds to aFunction<T,R>
instance accepting anEmployee
object as input and returns an employee name - the sort key. - Using
comparatorObj
for sorting results in a correctly sortedEmployee
list 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 - [su_note note_color="#ffffcc" text_color="#ffffff"]Comparator
[/su_note]empNameComparator = Comparator.comparing(Employee::getName);
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.
[su_box title="Comparator's thenComparing() method for multiple sort criteria" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]public static void main(String[] args) {
Comparator<Employee> empNameComparator = Comparator.comparing(Employee::getName).thenComparing(Employee::getAge);
Collections.sort(employeeList, empNameComparator);
employeeList.forEach(System.out::println);
}[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"]Employee Name:Deborah Sprightly Age:29
Employee Name:Ethan Hardy Age:65
Employee Name:Harry Major Age:25
Employee Name:Harry Major Age:35
Employee Name:Nancy Smith Age:15
Employee Name:Tom Jones Age:45
[/su_note]
Quick Explanation of the above code-
- First
comparing()
method is invoked with method reference forEmployee
'sgetName()
method. This returns aComparator
instance with the first level sort based onEmployee
name as we saw in previous section. - We append
.thenComparing(Employee::getAge)
to theComparator
instance returned usingcomparing()
method, which adds a second level sort based onEmployee
'sgetAge()
method. - The output is as expected with the employee named ‘Harry Major’ with the lesser age placed earlier in the sorted
employeeList
than his elder namesake employee.
Comparable
interface. Comparator provides two static methods naturalOrder()
and 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()
AND
static <T extends Comparable<? super T>> Comparator<T> reverseOrder()
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.)
[su_box title="Java 8 Comparator natural order sorting" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]public static void main(String[] args) {
List<String> empNames = employeeList.stream().map(Employee::getName).collect(Collectors.toList());
empNames.sort(Comparator.naturalOrder());
empNames.forEach(System.out::println);[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"] Deborah Sprightly
Ethan Hardy
Harry Major
Harry Major
Nancy Smith
Tom Jones[/su_note]
Quick Explanation of the above code-
- Employee names are extracted into an
empNames List
as explained before the code snippet. - empNames List is then sorted using the
Comparator.naturalOrder()
method which returns aComparator
instance ofString
’s natural comparison order based on theempName
’s generic type ofString
. - Employee names are sorted in natural comparison order, i.e. alphabetical order, and printed.
- Instead of invoking the
Comparator.naturalOrder()
method to theempNames.sort()
method, you can invoke theComparator.reverseOrder()
method in order to sort theempNames List
in reverse of natural comparison order or reverse alphabetical order.
nullsFirst()
and 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)
AND
static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)
nullsFirst()
and nullsLast()
handles sorting when some objects have sort-key as null.
[su_box title="Java 8 Comparator with sort key null and using nullsFirst()" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]public class ComparatorsInJava8 {
static List<Employee> employeeList = Arrays.asList(
new Employee(null, 45),
new Employee("Harry Major", 35),
new Employee("Harry Major", 25),
new Employee(null, 65),
new Employee("Nancy Smith", 15),
new Employee("Deborah Sprightly", 29));
public static void main(String[] args) {
Comparator<Employee> empNameComparator = Comparator.comparing(Employee::getName, Comparator.nullsFirst(String::compareTo));
Collections.sort(employeeList, empNameComparator);
employeeList.forEach(System.out::println);
}
}[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"]Employee Name:null Age:45
Employee Name:null Age:65
Employee Name:Deborah Sprightly Age:29
Employee Name:Harry Major Age:35
Employee Name:Harry Major Age:25
Employee Name:Nancy Smith Age:15
[/su_note]
Keeping the employee list same and invoking the Comparator
using nullLast()
-
[su_box title="Java 8 Comparator with sort key null and using nullsLast()" style="soft" box_color="#fcba43" title_color="#00000" radius="4" Class="for-shortcodebox"][java]public static void main(String[] args) {
Comparator<Employee> empNameComparator = Comparator.comparing(Employee::getName, Comparator.nullsLast(String::compareTo));
Collections.sort(employeeList, empNameComparator);
employeeList.forEach(System.out::println);
}
}[/java][/su_box]
OUTPUT of the above code[su_note note_color="#1a1a1a" text_color="#DAD9D7"]Employee Name:Deborah Sprightly Age:29
Employee Name:Harry Major Age:35
Employee Name:Harry Major Age:25
Employee Name:Nancy Smith Age:15
Employee Name:null Age:45
Employee Name:null Age:65
[/su_note]
Quick Explanation of the above code-
- Using
nullsFirst()
, the comparator places the twoEmployee
objects with the sort key null (employee name null) before the other Employee objects in the list. - Likewise,
nullsLast()
, the comparator places these twoEmployee
objects after the other Employee objects in the list.
Comparator.reversed()
default method.
The reversed()
method has the signature -
default Comparator<T> reversed()
- Comparator's default method
reversed()
is applied to theempNameComparator
which has been initially defined to sort in alphabetical order ofEmployee
names. - Due to
reversed()
being applied to it, theEmployee
objects are printed in reverse alphabetical order of their names.
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 - naturalOrder()
and reverseOrder()
. Next we looked at the null-valued sort key handling methods - nullsFirst()
and nullsLast()
. Lastly, we understood the reversed()
method which allows us to sort in the reverse order of a defined Comparator
.