Learn how to use Java Lambda Expression-based Comparator to sort collections in forward and reverse directions easily.
Tutorial Contents
Overview
The Collection’s elements must be compared by default to sort a collection. When the collection elements belong to Java predefined data types, we do not need to provide the comparison logic. On the other hand, when we have a collection of a custom object, we have to provide a comparison strategy.
One way to do that is to create a Comparator implementation for our object and write the comparison logic in the compare() method. In this tutorial, we will learn How to use Java Lambda Expressions to sort collections by providing inline comparator implementations.
Prepare a Collection
Let’s first prepare a Collection that we will sort throughout this tutorial. Assume our Collection holds records of student objects where the Student object has only a few fields.
public class Student {
private final Long studentId;
private final String firstName;
private final String lastName;
private final Integer age;
// Constructor, Getter, and Setter
}
Code language: Java (java)
Now, we will create a few dummy student records and put them in a simple ArrayList instance.
Student student1 = new Student(2L, "Karl", "F", 18);
Student student2 = new Student(3L, "Jack", "P", 20);
Student student3 = new Student(5L, "Nick", "G", 17);
Student student4 = new Student(1L, "Tom", "F", 21);
Student student5 = new Student(4L, "Jon", "W", 22);
students = new ArrayList<>();
students.add(student1);
students.add(student2);
students.add(student3);
students.add(student4);
students.add(student5);
Code language: Java (java)
Note that the records in the list are in random order.
Sort Without Lambda Expressions
The basic way to sort List elements is to use the sort() method. However, if the list contains custom objects – like ours, we have to provide a comparator instance that can compare between the instances of a custom object.
Next is an example of Sorting a collection using Anonymous inner class or inline implementation of Comparator.
students.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getFirstName().compareTo(o2.getFirstName());
}
});
students.forEach(System.out::println);
//prints:
//Student(studentId=3, firstName=Jack, lastName=P, age=20)
//Student(studentId=4, firstName=Jon, lastName=W, age=22)
//Student(studentId=2, firstName=Karl, lastName=F, age=18)
//Student(studentId=5, firstName=Nick, lastName=G, age=17)
//Student(studentId=1, firstName=Tom, lastName=F, age=21)
Code language: Java (java)
The inline implementation of the Comparator interface compares two Student objects based on the first name field.
Sort Using Basic Lambda Expression
Java Lambda Expressions help reduce a lot of builder plate code blocks and make the code concise. As the Comparator is a Java Functional Interface, we can use a lambda expression instead of passing an inline implementation class.
students.sort((o1, o2) ->
o1.getFirstName().compareTo(o2.getFirstName()));
Code language: Java (java)
Here, we have provided a lambda expression for the compare() method of the Comparator. We can see the code is now much cleaner.
Sort Using Method Reference
In the previous example, we provided an inline implementation of the Comparator in the form of a Java lambda expression. However, instead of putting the sorting logic inline, we can put that in a reusable method and use a method reference to sort Collections based on that logic.
For example, let’s create a Student Sort Utility class.
public class StudentSortUtils {
public static int comparingFirstName(
Student student1,
Student student2) {
return student1.getFirstName()
.compareTo(student2.getFirstName());
}
public static int comparingFirstAndLastName(
Student student1,
Student student2) {
return
(student1.getFirstName().equals(student2.getFirstName()))
? comparingFirstName(student1, student2)
: student1.getLastName().compareTo(student2.getLastName());
}
}
Code language: Java (java)
Here, we have two versions of the compare methods. Both accept the same arguments and return the same type as the Comparator#compare() method. However, they have a different comparison strategy.
Now, we can sort a collection using Method Reference, like this.
students
.sort(StudentSortUtils::comparingFirstName);
Code language: Java (java)
Alternatively, we can also sort using multiple fields with the respective method reference.
<meta charset="utf-8">students
.sort(StudentSortUtils::comparingFirstAndLastName);
Code language: Java (java)
Sort Using Comparator Factory Method
The Java Comparator interface has a static factory method of comparing(). The comparing() method accepts a key extractor function and builds a Comparator instance dynamically that compares the given key.
For example, if we want to sort Students based on age, we can create a Comparator using its static factory method comparing() like this:
Comparator.comparing(student -> student.getAge())
Code language: Java (java)
However, Java Lambda expressions allow us to replace the lambda expression with a direct method reference.
students.sort(Comparator.comparing(Student::getAge));
Code language: Java (java)
Sorting Based on Multiple Fields
When we want to sort a collection based on multiple fields, we can create a composition of multiple conditional expressions. For example, let’s sort a Students collection by last name and first name.
Our lambda expression will look like this.
(o1, o2) -> {
if (o1.getLastName().equals(o2.getLastName())) {
return o1.getFirstName().compareTo(o2.getFirstName());
} else {
return o1.getLastName().compareTo(o2.getLastName());
}
});
Code language: Java (java)
Alternatively, the Comparator supports compositing multiple Comparator instances together. Using that, we can sort a collection with multiple fields.
students.sort(
Comparator.comparing(Student::getLastName)
.thenComparing(Student::getFirstName)
);
students.forEach(System.out::println);
//prints:
//Student(studentId=1, firstName=Tom, lastName=F, age=21)
//Student(studentId=5, firstName=Nick, lastName=G, age=17)
//Student(studentId=3, firstName=Jack, lastName=P, age=20)
//Student(studentId=4, firstName=Jon, lastName=W, age=22)
Code language: Java (java)
Here, we used the Comparator#comparing() factory method to create a last name-based comparator and used the thenComparaing() – another factory method that compares based on the first name. Both of these comparators will be logically composed in a single Comparator instance.
Sort using Reverse Sorting (Descending Order)
So far, we have sorted the list of students based on various fields in ascending order. This section will discuss sorting fields in reverse or descending order.
Reverse Sort with Lambda Expression
When we use lambda expressions for Comparators, we provide our comparison logic. We must reverse the lambda expression to sort the fields in descending order.
Example of reverse sorting a Collection of custom objects.
students.sort((o1, o2) ->
o2.getFirstName().compareTo(o1.getFirstName()));
students.forEach(System.out::println);
//prints:
//Student(studentId=1, firstName=Tom, lastName=F, age=21)
//Student(studentId=5, firstName=Nick, lastName=G, age=17)
//Student(studentId=2, firstName=Karl, lastName=F, age=18)
//Student(studentId=4, firstName=Jon, lastName=W, age=22)
//Student(studentId=3, firstName=Jack, lastName=P, age=20)
Code language: Java (java)
Remember that we get forward sorting if we compare the first student to the second. To achieve reverse sorting, we must compare the second Students to the first.
Reverse Sort using Comparator reverseOrder()
Alternatively, if we use Comparator‘s static factory methods, we can use the static Comparator#reverseOrder() to instruct the reverse sorting order.
students.sort(Comparator.comparing(
Student::getAge,
Comparator.reverseOrder()));
students.forEach(System.out::println);
//prints:
//Student(studentId=4, firstName=Jon, lastName=W, age=22)
//Student(studentId=1, firstName=Tom, lastName=F, age=21)
//Student(studentId=3, firstName=Jack, lastName=P, age=20)
//Student(studentId=2, firstName=Karl, lastName=F, age=18)
//Student(studentId=5, firstName=Nick, lastName=G, age=17)
Code language: Java (java)
Note that we have provided an additional parameter to instruct the reverse sorting.
Sort with Comparator and Mixed Sorting Order
In addition, we can use reverse sorting along with Comparator compositions to make even more complex sorting expressions. Like, we want to sort collections in ascending order of a field and descending order of the other.
students.sort(Comparator.comparing(
Student::getLastName)
.thenComparing(Student::getAge, Comparator.reverseOrder())
);
students.forEach(System.out::println);
//prints:
//Student(studentId=1, firstName=Tom, lastName=F, age=21)
//Student(studentId=2, firstName=Karl, lastName=F, age=18)
//Student(studentId=5, firstName=Nick, lastName=G, age=17)
//Student(studentId=3, firstName=Jack, lastName=P, age=20)
//Student(studentId=4, firstName=Jon, lastName=W, age=22)
Code language: Java (java)
Our Comparator compositions with mixed sorting order have produced the intended output.
Summary
This was an overview of using Java Lambda-based Comparator expressions to sort collections. We started with an example of sorting a collection without lambda expression, where we had to provide an anonymous inline implementation of the Comparator interface.
Next, we understood how using Lambda expressions for the same sort operation makes the code concise. We also covered using the Comparator interface’s static factory methods. The factory methods create a Comparator instance based on the given fields and conditions.
Then we wrote some complex comparison operations like sorting based on multiple fields of a collection. Lastly, we touched upon doing reverse sort with lambda expressions and Comparator instances and also covered implementing a mixed sort order.
For the complete source of the examples used here, please visit our GitHub Repository.