1. Overview
Java 8 introduced several enhancements to the Comparator interface, including a handful of static functions that are of great utility when coming up with a sort order for collections.
Java 8 lambdas can be leveraged effectively with the Comparator interface as well. A detailed explanation of lambdas and Comparator can be found here, and a chronicle on sorting and applications of Comparator can be found here.
In this tutorial, we will explore several functions introduced for the Comparator interface in Java 8.
2. Getting Started
2.1. Sample Bean Class
For the examples in this article, let’s create an Employee bean and use its fields for comparing and sorting purposes:
public class Employee {
String name;
int age;
double salary;
long mobile;
// constructors, getters & setters
}
2.2. Our Testing Data
Let’s also create an array of employees that will be used to store the results of our type in various test cases throughout the article:
employees = new Employee[] { ... };
The initial ordering of elements of employees will be:
[Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Throughout the article, we’ll be sorting above Employee array using different functions.
For test assertions, we’ll use a set of pre-sorted arrays that we will compare to our sort results (i.e., the employees array) for different scenarios.
Let’s declare a few of these arrays:
@Before
public void initData() {
sortedEmployeesByName = new Employee[] {...};
sortedEmployeesByNameDesc = new Employee[] {...};
sortedEmployeesByAge = new Employee[] {...};
// ...
}
Like always, feel free to refer our GitHub link for the complete code.
3. Using Comparator.comparing
This section covers variants of the Comparator.comparing static function.
3.1. Key Selector Variant
The Comparator.comparing static function accepts a sort key Function and returns a Comparator for the type which contains the sort key:
static <T,U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T,? extends U> keyExtractor)
To see this in action, let’s use the name field in Employee as the sort key and pass its method reference as an argument of type Function. The Comparator returned from the same is used for sorting:
@Test
public void whenComparing_thenSortedByName() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByName));
}
As you can see, the employees array values are sorted by name as a result of the sort:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
3.2. Key Selector and Comparator Variant
There is another option that facilitates overriding the natural ordering of the sort key by providing the Comparator that creates a custom ordering for the sort key:
static <T,U> Comparator<T> comparing(
Function<? super T,? extends U> keyExtractor,
Comparator<? super U> keyComparator)
Let’s modify the test above, overriding the natural order of sorting by the name field by providing a Comparator for sorting the names in descending order as the second argument to Comparator.comparing:
@Test
public void whenComparingWithComparator_thenSortedByNameDesc() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(
Employee::getName, (s1, s2) -> {
return s2.compareTo(s1);
});
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc));
}
As you can see, the results are sorted in descending order by name:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
3.3. Using Comparator.reversed
When invoked on an existing Comparator, the instance method Comparator.reversed returns a new Comparator that reverses the sort order of the original.
Let’s use the Comparator that sorts the employees by name and reverse it so that employees are sorted in descending order of the name:
@Test
public void whenReversed_thenSortedByNameDesc() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator<Employee> employeeNameComparatorReversed
= employeeNameComparator.reversed();
Arrays.sort(employees, employeeNameComparatorReversed);
assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc));
}
The results are sorted in descending order by name:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
3.4. Using Comparator.comparingInt
There is also a function Comparator.comparingInt which does the same thing as Comparator.comparing, but it takes only int selectors. Let’s try this with an example where we order employees by age:
@Test
public void whenComparingInt_thenSortedByAge() {
Comparator<Employee> employeeAgeComparator
= Comparator.comparingInt(Employee::getAge);
Arrays.sort(employees, employeeAgeComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByAge));
}
Let’s see how the employees array values are ordered after the sort:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
3.5. Using Comparator.comparingLong
Similar to what we did for int keys, let’s see an example using Comparator.comparingLong to consider a sort key of type long by ordering the employees array by the mobile field:
@Test
public void whenComparingLong_thenSortedByMobile() {
Comparator<Employee> employeeMobileComparator
= Comparator.comparingLong(Employee::getMobile);
Arrays.sort(employees, employeeMobileComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByMobile));
}
Let’s see how the employees array values are ordered after the sort with mobile as the key:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001)]
3.6. Using Comparator.comparingDouble
Again, similar to what we did for int and long keys, let’s see an example using Comparator.comparingDouble to consider a sort key of type double by ordering the employees array by the salary field:
@Test
public void whenComparingDouble_thenSortedBySalary() {
Comparator<Employee> employeeSalaryComparator
= Comparator.comparingDouble(Employee::getSalary);
Arrays.sort(employees, employeeSalaryComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesBySalary));
}
Let’s see how the employees array values are ordered after the sort with salary as the sort key:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
4. Considering Natural Order in Comparator
The natural order is defined by the behavior of the Comparable interface implementation. More information about the difference between Comparator and uses of the Comparable interface can be found in this article.
Let’s implement Comparable in our Employee class so that we can try the natrualOrder and reverseOrder functions of the Comparator interface:
public class Employee implements Comparable<Employee>{
// ...
@Override
public int compareTo(Employee argEmployee) {
return name.compareTo(argEmployee.getName());
}
}
4.1. Using Natural Order
The naturalOrder function returns the Comparator for the return type mentioned in the signature:
static <T extends Comparable<? super T>> Comparator<T> naturalOrder()
Given the above logic to compare employees based on name field, let’s use this function to obtain to a Comparator which sorts the employees array in natural order:
@Test
public void whenNaturalOrder_thenSortedByName() {
Comparator<Employee> employeeNameComparator
= Comparator.<Employee> naturalOrder();
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByName));
}
Let’s see how the employees array values are ordered after the sort:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
4.2. Using Reverse Natural Order
Similar to naturalOrder, let’s use the reverseOrder method to generate a Comparator which will produce a reverse ordering of employees to the one in the naturalOrder example:
@Test
public void whenReverseOrder_thenSortedByNameDesc() {
Comparator<Employee> employeeNameComparator
= Comparator.<Employee> reverseOrder();
Arrays.sort(employees, employeeNameComparator);
assertTrue(Arrays.equals(employees, sortedEmployeesByNameDesc));
}
Let’s see how the employees array values are ordered after the sort:
[Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001)]
5. Considering Null Values in Comparator
This section covers functions nullsFirst and nullsLast, which consider null values in ordering and keep the null values at the beginning or end of the ordering sequence.
5.1. Considering Null First
Let’s randomly insert null values in employees array:
[Employee(name=John, age=25, salary=3000.0, mobile=9922001),
null,
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
null,
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
The nullsFirst function will return a Comparator that keeps all nulls at the beginning of the ordering sequence:
@Test
public void whenNullsFirst_thenSortedByNameWithNullsFirst() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator<Employee> employeeNameComparator_nullFirst
= Comparator.nullsFirst(employeeNameComparator);
Arrays.sort(employeesArrayWithNulls,
employeeNameComparator_nullFirst);
assertTrue(Arrays.equals(
employeesArrayWithNulls,
sortedEmployeesArray_WithNullsFirst));
}
Let’s see how the employees array values are ordered after the sort:
[null,
null,
Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
5.2. Considering Null Last
The nullsLast function will return a Comparator that keeps all nulls at the end of the ordering sequence:
@Test
public void whenNullsLast_thenSortedByNameWithNullsLast() {
Comparator<Employee> employeeNameComparator
= Comparator.comparing(Employee::getName);
Comparator<Employee> employeeNameComparator_nullLast
= Comparator.nullsLast(employeeNameComparator);
Arrays.sort(employeesArrayWithNulls, employeeNameComparator_nullLast);
assertTrue(Arrays.equals(
employeesArrayWithNulls, sortedEmployeesArray_WithNullsLast));
}
Let’s see how the employees array values are ordered after the sort:
[Employee(name=Ace, age=22, salary=2000.0, mobile=5924001),
Employee(name=John, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401),
null,
null]
6. Using Comparator.thenComparing
The thenComparing function lets you set up lexicographical ordering of values by provisioning multiple sort keys in a particular sequence.
Let’s consider another array of Employee class:
someMoreEmployees = new Employee[] { ... };
Consider the following sequence of elements in the above array:
[Employee(name=Jake, age=25, salary=3000.0, mobile=9922001),
Employee(name=Jake, age=22, salary=2000.0, mobile=5924001),
Employee(name=Ace, age=22, salary=3000.0, mobile=6423001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Let’s write a sequence of comparisons as age followed by the name and see the ordering of this array:
@Test
public void whenThenComparing_thenSortedByAgeName(){
Comparator<Employee> employee_Age_Name_Comparator
= Comparator.comparing(Employee::getAge)
.thenComparing(Employee::getName);
Arrays.sort(someMoreEmployees, employee_Age_Name_Comparator);
assertTrue(Arrays.equals(someMoreEmployees, sortedEmployeesByAgeName));
}
Here the ordering will be done by age, and for the values with the same age, ordering will be done by name. Let’s observe this in the sequence we receive after sorting:
[Employee(name=Ace, age=22, salary=3000.0, mobile=6423001),
Employee(name=Jake, age=22, salary=2000.0, mobile=5924001),
Employee(name=Jake, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Let’s use the other version of thenComparing that is thenComparingInt, by changing the lexicographical sequence to name followed by age:
@Test
public void whenThenComparing_thenSortedByNameAge() {
Comparator<Employee> employee_Name_Age_Comparator
= Comparator.comparing(Employee::getName)
.thenComparingInt(Employee::getAge);
Arrays.sort(someMoreEmployees, employee_Name_Age_Comparator);
assertTrue(Arrays.equals(someMoreEmployees,
sortedEmployeesByNameAge));
}
Let’s see how the employees array values are ordered after the sort:T
[Employee(name=Ace, age=22, salary=3000.0, mobile=6423001),
Employee(name=Jake, age=22, salary=2000.0, mobile=5924001),
Employee(name=Jake, age=25, salary=3000.0, mobile=9922001),
Employee(name=Keith, age=35, salary=4000.0, mobile=3924401)]
Similarly, there are functions thenComparingLong and thenComparingDouble for using long and double sorting keys.
7. Conclusion
This article is a guide to several features introduced in Java 8 for the Comparator interface.
As usual, the source code can be found over on Github.