Java List Sorting: Comparable and Comparator Examples

Learn to sort a List of objects by a field value in Java using the Comparable interface (default sort order) and Comparator interface (additional custom sort orders).

List list = ...;
Comparator comparator = Comparator.reverseOrder(); //Create custom order as needed

//1 - List.sort()
list.sort(null);
list.sort(comparator);

//2 - Collections.sort()
Collections.sort(list);
Collections.sort(list, comparator);

//3 - Stream.sorted()
List sortedList = list.stream().sorted().toList(); //or
List reverseList = list.stream().sorted(comparator).toList();

Note that if we have millions of records for sorting at a time then a database query is the best way. Otherwise, using either Comparable or Comparator interface is a very convenient approach.

1. Setup

In the examples given in this tutorial, we will be using the record type User. It has four fields: id, firstName, lastName and age. I have chosen these fields purposefully to show different usecases.

import java.io.Serializable;

public record User(Long id, String firstName, String lastName, Integer age) 
        implements Serializable {

    public User {
        if (age < 18) {
            throw new IllegalArgumentException("You cannot hire a minor person");
        }
    }
}

We will be using the given unsorted list and sorting it on different field values.

private static List<User> getUnsortedUsers() {
    return Arrays.asList(
            new User(1L, "A", "Q", Integer.valueOf(24)),
            new User(4L, "B", "P", Integer.valueOf(22)),
            new User(2L, "C", "O", Integer.valueOf(27)),
            new User(3L, "D", "N", Integer.valueOf(29)),
            new User(5L, "E", "M", Integer.valueOf(25)));
}

Moving on, we will be using the Comparable and Comparator interfaces for sorting on different field values.

2. Sorting a List with Comparable for Natural Ordering

2.1. Implementing Comparable Interface

Comparable interface provides a single method compareTo(T o) to implement by any class so that two objects of that class can be compared. This method is used for implementing the natural sorting behavior.

The User record after implementing the Comparable interface is as follows. The similar implementation can be done for class types as well. The default sorting has been done on the id field.

public record User(Long id, String firstName, String lastName, Integer age) 
        implements Serializable, Comparable<User> {

    public User {
        if (age < 18) {
            throw new IllegalArgumentException("You cannot hire a minor person");
        }
    }

    @Override
    public int compareTo(User o) {
        return this.id.intValue() - o.id.intValue();
    }
}

2.2. List.sort() Method

This method sorts the current list according to the order induced by the specified Comparator. It is important that all elements in this list must be mutually comparable using the specified comparator. This method modifies the list in place.

list.sort(Comparator.naturalOrder());

If the specified comparator is null then all elements in the list must implement the Comparable interface and the elements’ natural ordering will be used.

2.3. Collections.sort() Method

We can pass the List of objects in the sort() method that will sort the objects in their natural ordering i.e. by id field.

Collections.sort( list );

Check out the output in the console.

[User[id=1, firstName=A, lastName=Q, age=24], 
User[id=2, firstName=C, lastName=O, age=27], 
User[id=3, firstName=D, lastName=N, age=29], 
User[id=4, firstName=B, lastName=P, age=22], 
User[id=5, firstName=E, lastName=M, age=25]]

2.4. Stream.sorted() Method

Java Stream API has sorted() method that can sort a stream of items in the natural order. Note that stream operations do not modify the original collections, so the objects in the list will be unchanged.

List<User> sortedList = list.stream()
                          .sorted()
                          .collect(Collectors.toList());

3. Sorting a List with a Comparator for Custom Ordering

3.1. Creating Comparator Instances

Let us assume that we want to sort the users list based on some other fields, for example, by firstName or age. We can modify the User record because it already implements the natural ordering by id field.

Here comes the Comparator interface to rescue. A Comparator can be used to define the custom ordering. To sort on different object fields, we can create multiple Comparator implementations.

For example, to sort the users list by firstName, we can create FirstNameSorter class that implements the Comparator.

import java.util.Comparator;

public class FirstNameSorter implements Comparator<User> {

    @Override
    public int compare(User o1, User o2) {
        return o1.firstName().compareTo(o2.firstName());
    }
}

Note that we can use the lambda expression for creating the inline Comparator instances, for single-time uses.

Comparator<User> firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName());

We can create group by sorting effect by combining multiple comparators using Comparator.thenComparing() method. For example, we can create a complex comparator fullNameSorter for sorting a list by first name and then by last name.

Comparator<User> firstNameSorter = (o1, o2) -> o1.firstName().compareTo(o2.firstName());
Comparator<User> lastNameSorter = (o1, o2) -> o1.lastName().compareTo(o2.lastName());

Comparator<User> fullNameSorter = firstNameSorter.thenComparing(lastNameSorter);

One more type of Comparator is worth discussing that is used for reverse ordering. We can get this reverse comparator by calling reversed() method on any comparator instance.

Comparator<User> reverseSorter = firstNameSorter.reversed();

Similar way, we can create as many comparators as needed in the applications.

3.2. Collections.sort()

To sort using Collection.sort() method, pass two method arguments. The first argument is the unsorted list and the second argument is the Comparator instance.

List<User> list = getUnsortedUsers();
Comparator<User> firstNameSorter 
	= (o1, o2) -> o1.firstName().compareTo(o2.firstName());

Collections.sort(list, firstNameSorter);

3.3. Stream.sorted()

To sort the stream items using comparator instance, we can pass the comparator as method argument to the sorted() method.

List<User> list = getUnsortedUsers();
Comparator<User> firstNameSorter 
	= (o1, o2) -> o1.firstName().compareTo(o2.firstName());

List<User> sortedList = list.stream()
                .sorted(firstNameSorter)
                .collect(Collectors.toList());

4. Honor the hashCode() and equals() Contract

If we have overridden equals() method in the User class, always remember to honor the contract between hashCode() and equals() methods.

If two objects are equal using equals() method then compareTo() method should return zero.

As a general practice, always use the same fields in both methods. If we are using id field in the equals() method then use the id field in compareTo() method also. An example implementation is given as follows:

import java.io.Serializable;
import java.util.Objects;

public record User(Long id, String firstName, String lastName, Integer age) 
        implements Serializable, Comparable<User> {

    public User {
        if (age < 18) {
            throw new IllegalArgumentException("You cannot hire a minor person");
        }
    }
    
    @Override
    public int compareTo(User o) {
        return this.id.intValue() - o.id.intValue();
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        return Objects.equals(id, other.id);
    }
}

5. Conclusion

In this Java Comparable and Comparator tutorial, we learned to implement both interfaces in different ways for different usecases. We also saw the use of both interfaces in Java Stream API.

Finally, we understood how to correctly override hashCode() and equals() method on objects to keep sorting functioning properly.

Happy Learning !!

Comments

Subscribe
Notify of
guest
30 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.