Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

> CHECK OUT THE COURSE

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.

The Comparator interface can also effectively leverage Java 8 lambdas. A detailed explanation of lambdas and Comparator can be found here, and a chronicle on the applications of Comparator and sorting can be found here.

In this tutorial, we'll explore several functions introduced for the Comparator interface in Java 8.

2. Getting Started

2.1. Sample Bean Class

For the examples in this tutorial, 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

We'll also create an array of employees that we'll use to store the results of our type in various test cases throughout the tutorial:

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 tutorial, we'll be sorting the above Employee array using different functions.

For test assertions, we'll use a set of pre-sorted arrays that we'll 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[] {...};
    
    // ...
}

As always, feel free to refer to our GitHub link for the complete code.

3. Using Comparator.comparing

In this section, we'll cover 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 that 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, we'll 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 a result of the sort, the employees array values are in order by name:

[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's another option that facilitates overriding the natural ordering of the sort key by providing a 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)

So let's modify the test above. We'll override 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 we 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.

We'll use the Comparator that sorts the employees by name and reverse it so that the 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));
}

Now 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's also a function, Comparator.comparingInt, which does the same thing as Comparator.comparing, but it takes only int selectors. Let's try it 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));
}

After the sort, the employees array values have the following order:

[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 look at 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));
}

After the sort, the employees array values have the following order 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, as we did for int and long keys, let's look at 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));
}

After the sort, the employees array values have the following order 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

We can define the natural order by the behavior of the Comparable interface implementation. More information about the differences between Comparator and the 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 naturalOrder 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 the name field, let's use this function to obtain 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));
}

After the sort, the employees array values have the following order:

[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 how we used naturalOrder, we'll use the reverseOrder method to generate a Comparator that will produce a reverse ordering of employees compared 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));
}

After the sort, the employees array values have the following order:

[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

In this section, we'll cover the nullsFirst and nullsLast functions, 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 the 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));
}

After the sort, the employees array values have the following order:

[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));
}

After the sort, the employees array values have the following order:

[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 us set up lexicographical ordering of values by provisioning multiple sort keys in a particular sequence.

Let's look at another array of the Employee class:

someMoreEmployees = new Employee[] { ... };

We'll 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)]

Then we'll write a sequence of comparisons as the 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, the ordering will be done by name. We can see 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)]

Now we can use the other version of thenComparing, 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));
}

After the sort, the employees array values have the following order:

[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, the functions thenComparingLong and thenComparingDouble are for using long and double sorting keys, respectively.

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.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
Comments are closed on this article!