Sorting in java 8

In Java 8 sorting has been simplified by removing the verbose comparator code and anonymous inner classes. Lets take a look at how making it easier could drive a different behavior among java developers.

Detailed Video Notes

Hey everyone, this is Justin from Level Up Lunch. In today's episode we will look at how we can sort a collection of objects using java 8 syntax. To get us started, we created an Employee class with a few different attributes such as employee number, first name, last name and hire date. In our EmployeeTest class we created a List of employees with seed data that we will use in our snippets below.

public class Employee {

    private Integer employeeNumber;
    private String employeeFirstName;
    private String employeeLastName;
    private LocalDate hireDate;

    //..
}

public class EmployeeTest {

    List<Employee> employees;

    @Before
    public void setup() {

        employees = new ArrayList<>();
        employees.add(new Employee(123, "Jack", "Johnson", LocalDate.of(1988, Month.APRIL, 12)));
        employees.add(new Employee(345, "Cindy", "Bower", LocalDate.of(2011, Month.DECEMBER, 15)));
        employees.add(new Employee(567, "Perry", "Node", LocalDate.of(2005, Month.JUNE, 07)));
        employees.add(new Employee(467, "Pam", "Krauss", LocalDate.of(2005, Month.JUNE, 07)));
        employees.add(new Employee(435, "Fred", "Shak", LocalDate.of(1988, Month.APRIL, 17)));
        employees.add(new Employee(678, "Ann", "Lee", LocalDate.of(2007, Month.APRIL, 12)));
    }

    //..
}

Before java 8

Prior to java 8, if you ever had to sort a collection there was two different ways you might of approached this. You probably created this very verbose comparator that you will pass around to Collection.sort. What was difficult is getting a comparator because it might of existed in another package or dependency that your product didn't depend on which could lead to duplication of code or not even creating one. This complexity would drive java developers behavior to make another call to a database instead of creating a comparator and sorting in memory. There is times when you want to leverage the database to apply sorting, possibly when your result set is to large to handle in memory, dynamic fields and when you want to apply ascending and descending sort order. With Java 8 it has become much more simpler because it allows you to write more concise code.

A sample comparator

Comparator<Employee> byHireDate = new Comparator<Employee>() {
    public int compare(Employee left, Employee right) {
        if (left.getHireDate().isBefore(right.getHireDate())) {
            return -1;
        } else {
            return 1;
        } 
    }
};

Collections.sort(employees, byHireDate);

Sorting with anonymous inner class

The other approach is to create an anonymous inner class that you could inline to the Collections.sort which reduce some of the verbosity but made this very inflexible in the way of reuse. In the case you wanted to reverse the comparator, you would need to create a new anonymous inner class to handle the situation passing it to Collections.sort.

Collections.sort(employees, new Comparator<Employee>() {
    @Override
    public int compare(Employee o1, Employee o2) {
        return o1.getEmployeeFirstName().compareTo(
                o2.getEmployeeFirstName());
    }
});

Sort by employee number

[1:57]

To get us started with java 8 syntax we created a few eclipse snippets to save us from some typing. Our first example is to sort by employee number. In java 8 you can create a comparator in the form of a lambda expression. You might be asking how the compiler knows to convert the lambda expression to a Comparator? It knows through type inference of the type of object and the Comparator has a annotation applied called @FunctionalInterface. The Comparator describes a function descriptor with the signature(T,T) -> int. So when we looking at the lambda expression, it will have two parameters of employee object and then it will compare the employee numbers of the specified objects.

So to apply the sort, we will call employees.stream().sorted passing in the comparator. In the stream interface there is two sorted methods, one with a comparator parameter and the second without a parameter which sorts on the natural order of the objects.

@Test
public void java_8() {
    
    Comparator<Employee> byEmployeeNumber = (e1, e2) -> Integer.compare(
            e1.getEmployeeNumber(), e2.getEmployeeNumber());

    employees.stream().sorted(byEmployeeNumber)
            .forEach(e -> System.out.println(e));      
}

Output

Employee{employeeNumber=123, employeeFirstName=Jack, employeeLastName=Johnson, hireDate=1988-04-12}
Employee{employeeNumber=345, employeeFirstName=Cindy, employeeLastName=Bower, hireDate=2011-12-15}
Employee{employeeNumber=435, employeeFirstName=Fred, employeeLastName=Shak, hireDate=1988-04-17}
Employee{employeeNumber=467, employeeFirstName=Pam, employeeLastName=Krauss, hireDate=2005-06-07}
Employee{employeeNumber=567, employeeFirstName=Perry, employeeLastName=Node, hireDate=2005-06-07}
Employee{employeeNumber=678, employeeFirstName=Ann, employeeLastName=Lee, hireDate=2007-04-12}

Inline lambda expression

[3:38]

The other way we could have written this, instead of defining a Comparator = lambda expression like above, we can pass the lambda expression inline to the sort method.

@Test
public void java_8() {

    employees
            .stream()
            .sorted((e1, e2) -> Integer.compare(e1.getEmployeeNumber(),
                    e2.getEmployeeNumber()))
            .forEach(e -> System.out.println(e));
}

Explicitly state class type in Lambda

In addition to what was described in the screencast, you can explicitly state the type within the lambda expression, it looks something like this:

Comparator<Employee> byEmployeeNumber = (Employee e1, Employee e2) -> Integer
                .compare(e1.getEmployeeNumber(), e2.getEmployeeNumber());

Longest tenured employee

[3:56]

The second example could be derived from a use case, 'Get the longest tenured employee'. In order to find this, we will want to order the collection by employee hire date. The first thing we will want to do is call employees.stream().sort and create a lambda expression that will compare employee's hire dates in ascending order putting the oldest hire date first.

@Test
public void filter_get_first() {

    employees
            .stream()
            .sorted((e1, e2) -> e1.getHireDate()
                    .compareTo(e2.getHireDate()))
            .forEach(e -> System.out.println(e));
}

Output

Employee{employeeNumber=123, employeeFirstName=Jack, employeeLastName=Johnson, hireDate=1988-04-12}
Employee{employeeNumber=435, employeeFirstName=Fred, employeeLastName=Shak, hireDate=1988-04-17}
Employee{employeeNumber=567, employeeFirstName=Perry, employeeLastName=Node, hireDate=2005-06-07}
Employee{employeeNumber=467, employeeFirstName=Pam, employeeLastName=Krauss, hireDate=2005-06-07}
Employee{employeeNumber=678, employeeFirstName=Ann, employeeLastName=Lee, hireDate=2007-04-12}
Employee{employeeNumber=345, employeeFirstName=Cindy, employeeLastName=Bower, hireDate=2011-12-15}

First element in Stream

Going back the use case of getting the person with the longest tenure, the second piece of this exercises is to get the first element in the steam or arraylist which would represent the longest tenured employee. We will do this by using a stream terminal operation called findFirst. findFirst will return an java 8 Optional which can be described as a wrapper around the employee object. If an employee object is found it will be present, if one isn't found the Optional object would be empty.

When we run this we will see that Jack Johnson is the longest tenured employee.

java.util.Optional<Employee> employee = employees
        .stream()
        .sorted((e1, e2) -> e1.getHireDate()
                .compareTo(e2.getHireDate())).findFirst();

System.out.println(employee.get());

Output

Employee{employeeNumber=123, employeeFirstName=Jack, employeeLastName=Johnson, hireDate=1988-04-12}

Multiple sort criteria

[6:18]

Prior to java 8 when you tried to sort by several comparators, it got ugly fast. So you might of created a single comparator with logic to compare various fields and the code readability goes down the tube. What is neat about java 8 is it implemented a way to concatenate or combine multiple comparators together by calling Comparator.thenComparing.

We will first create two comparators byFirstName and byLastName. Since we want first name sorted first we will call the thenComparing method on the byFirstName comparator. The thenComparing method will then return a comparator which we will pass into the sort method.

Let's take a look:

@Test
public void multiple_sort() {

    Comparator<Employee> byFirstName = (e1, e2) -> e1
            .getEmployeeFirstName().compareTo(e2.getEmployeeFirstName());

    Comparator<Employee> byLastName = (e1, e2) -> e1.getEmployeeLastName()
            .compareTo(e2.getEmployeeLastName());

    employees.stream().sorted(byFirstName.thenComparing(byLastName))
            .forEach(e -> System.out.println(e));
}

Output

Employee{employeeNumber=678, employeeFirstName=Ann, employeeLastName=Lee, hireDate=2007-04-12}
Employee{employeeNumber=345, employeeFirstName=Cindy, employeeLastName=Bower, hireDate=2011-12-15}
Employee{employeeNumber=435, employeeFirstName=Fred, employeeLastName=Shak, hireDate=1988-04-17}
Employee{employeeNumber=123, employeeFirstName=Jack, employeeLastName=Johnson, hireDate=1988-04-12}
Employee{employeeNumber=467, employeeFirstName=Pam, employeeLastName=Krauss, hireDate=2005-06-07}
Employee{employeeNumber=567, employeeFirstName=Perry, employeeLastName=Node, hireDate=2005-06-07}

Hopefully this was a good introduction to sorting or ordering in java 8. Thanks for joining in today's level up lunch, Have a great day!