Java 8 – How to Sort List with Stream.sorted() - Stack Abuse

Java 8 – How to Sort List with Stream.sorted()

Introduction

A stream represents a sequence of elements and supports different kind of operations that lead to the desired result.

The source of these elements is usually a Collection or an Array, from which data is provided to the stream.

Streams differ from collections in several ways; most notably in that the streams are not a data structure that stores elements. They're functional in nature, and it's worth noting that operations on a stream produce a result, but do not modify its source.

Sorting a List of Integers with Stream.sorted()

Found within the Stream interface, the sorted() method has two overloaded variations that we'll be looking into.

Both of these variations are instance methods, which require an object of its class to be created before it can be used:

public final Stream<T> sorted() {}

This methods returns a stream consisting of the elements of the stream, sorted according to natural order - the ordering provided by the JVM. If the elements of the stream are not Comparable, a java.lang.ClassCastException may be thrown upon execution.

Using this method is fairly simple, so let's take a look at a couple of examples:

Arrays.asList(10, 23, -4, 0, 18).stream().sorted().forEach(System.out::println);

Here, we make a List instance through the asList() method, providing a few integers and stream() them. Once streamed, we can run the sorted() method, which sorts these integers naturally. Once sorted, we've just printed them out, each in a line:

-4
0
10
18
23

If we wanted save the results of sorting after the program was executed, we would have to collect() the data back in a Collection (a List in this example), since sorted() doesn't modify the source.

Let's save this result into a sortedList:

List<Integer> list = Arrays.asList(10, 23, -4, 0, 18);
List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList());

System.out.println(list);
System.out.println(sortedList);

Running this code will produce:

[10, 23, -4, 0, 18]
[-4, 0, 10, 18, 23]

Here we see that the original list stayed unmodified, but we did save the results of the sorting in a new list, allowing us to use both if we need so later on.

Sorting a List of Integers in Descending Order with Stream.sorted()

Stream.sorted() by default sorts in natural order. In the case of our integers, this means that they're sorted in ascending order.

Sometimes, you might want to switch this up and sort in descending order. There are two simple ways to do this - supply a Comparator, and switch the order, which we'll cover in a later section, or simply use Collections.reverseOrder() in the sorted() call:

List<Integer> list = Arrays.asList(10, 23, -4, 0, 18);
List<Integer> sortedList = list.stream()
        .sorted(Collections.reverseOrder())
        .collect(Collectors.toList());

System.out.println(sortedList);

This results in:

[23, 18, 10, 0, -4]

Sorting a List of Strings with Stream.sorted()

Though, we don't always just sort integers. Sorting Strings is a tiny bit different, since it's a bit less intuitive on how to compare them.

Here, the sorted() method also follows the natural order, as imposed by the JVM. In case of Strings, they're sorted lexicographically:

Arrays.asList("John", "Mark", "Robert", "Lucas", "Brandon").stream().sorted().forEach(System.out::println);

Running this produces:

Brandon
John
Lucas
Mark
Robert

If we wanted the newly sorted list saved, the same procedure as with the integers applies here:

List<String> list = Arrays.asList("John", "Mark", "Robert", "Lucas", "Brandon");
List<String> sortedList = list.stream().sorted().collect(Collectors.toList());

System.out.println(sortedList);

This results in:

[Brandon, John, Lucas, Mark, Robert]

Git Essentials

Check out this hands-on, practical guide to learning Git, with best-practices and industry-accepted standards. Stop Googling Git commands and actually learn it!

Sorting Strings in reverse order is as simple as sorting integers in reverse order:

List<String> list = Arrays.asList("John", "Mark", "Robert", "Lucas", "Brandon");
List<String> sortedList = list.stream()
        .sorted(Collections.reverseOrder())
        .collect(Collectors.toList());
        
System.out.println(sortedList);

This results in:

[Robert, Mark, Lucas, John, Brandon]

Sorting Custom Objects with Stream.sorted(Comparator<? super T> comparator)

In all of the previous examples, we've worked with Comparable types. However, if we're working with some custom objects, which might not be Comparable by design, and would still like to sort them using this method - we'll need to supply a Comparator to the sorted() call.

Let's define a User class, which isn't Comparable and see how we can sort them in a List, using Stream.sorted():

public class User {
    
    private String name;
    private int age;

    // Constructor, getters, setters and toString()
}

In the first iteration of this example, let's say we want to sort our users by their age. If the age of the users is the same, the first one that was added to the list will be the first in the sorted order. Let's say we have the following code:

Let's sort them by age, first. If their age is the same, the order of insertion to the list is what defines their position in the sorted list:

List<User> userList = new ArrayList<>(Arrays.asList(
        new User("John", 33), 
        new User("Robert", 26), 
        new User("Mark", 26), 
        new User("Brandon", 42)));

List<User> sortedList = userList.stream()
        .sorted(Comparator.comparingInt(User::getAge))
        .collect(Collectors.toList());

sortedList.forEach(System.out::println);

When we run this, we get the following output:

User:[name: Robert, age: 26]
User:[name: Mark, age: 26]
User:[name: John, age: 33]
User:[name: Brandon, age: 42]

Here, we've made a list of User objects. We're streaming that list, and using the sorted() method with a Comparator. Specifically, we're using the comparingInt() method, and supplying the user's age, via the User::getAge method reference.

There are a few of these built-in comparators that work with numbers (int, double, and long) - comparingInt(), comparingDouble(), and comparingLong(). Ultimately, you can also just use the comparing() method, which accepts a sorting key function, just like the other ones.

All of them simply return a comparator, with the passed function as the sorting key. In our case, we're using the getAge() method as the sorting key.

We can easily reverse this order as well, simply by chaining the reversed() method after the comparingInt() call:

List<User> sortedList = userList.stream()
        .sorted(Comparator.comparingInt(User::getAge).reversed())
        .collect(Collectors.toList());

sortedList.forEach(System.out::println);

This results in:

User:[name: Brandon, age: 42]
User:[name: John, age: 33]
User:[name: Robert, age: 26]
User:[name: Mark, age: 26]

Defining a Custom Comparator with Stream.sorted()

While Comparators produced by methods such as comparing() and comparingInt(), are super-simple to work with and only require a sorting key - sometimes, the automated behavior is not what we're looking for.

If we sort the Users, and two of them have the same age, they're now sorted by the order of insertion, not their natural order, based on their names. Mark should be before Robert, in a list sorted by name, but in the list we've sorted previously, it's the other way around.

For cases like these, we'll want to write a custom Comparator:

List<User> userList = new ArrayList<>(Arrays.asList(
        new User("John", 33),
        new User("Robert", 26),
        new User("Mark", 26),
        new User("Brandon", 42)));

List<User> sortedList = userList.stream()
        .sorted((o1, o2) -> {
            if(o1.getAge() == o2.getAge())
                return o1.getName().compareTo(o2.getName());
            else if(o1.getAge() > o2.getAge())
                return 1;
            else return -1;
        })
        .collect(Collectors.toList());

sortedList.forEach(System.out::println);

And now, when we execute this code, we've got the natural order of names, as well as ages, sorted:

User:[name: Mark, age: 26]
User:[name: Robert, age: 26]
User:[name: John, age: 33]
User:[name: Brandon, age: 42]

Here, we've used a Lambda expression to create a new Comparator implicitly and defined the logic for sorting/comparison. Returning a positive number indicates that an element is greater than another. Returning a negative number indicates that an element is lesser than another.

We've used the respective comparison approaches for the names and ages - comparing names lexicographically using compareTo(), if the age values are the same, and comparing ages regularly via the > operator.

If you're not used to Lambda expressions, you can create a Comparator beforehand, though, for the sake of code readability, it's advised to shorten it to a Lambda:

Comparator<User> customComparator = new Comparator<User>() {
    @Override
    public int compare(User o1, User o2) {
        if(o1.getAge() == o2.getAge())
            return o1.getName().compareTo(o2.getName());
        else if(o1.getAge() > o2.getAge())
            return 1;
        else return -1;
    }
};

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

You can also technically make an anonymous instantiation of the comparator in the sorted() call:

List<User> sortedList = userList.stream()
        .sorted(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                if(o1.getAge() == o2.getAge())
                    return o1.getName().compareTo(o2.getName());
                else if(o1.getAge() > o2.getAge())
                    return 1;
                else return -1;
            }
        })
        .collect(Collectors.toList());

And this anonymous call is exactly what gets shortened to the Lambda expression from the first approach.

Conclusion

In this tutorial, we've covered everything you need to know about the Stream.sorted() method. We've sorted Comparable integers and Strings, in ascending and descending order, as well as used a built-in Comparator for custom objects.

Finally, we've used a custom Comparator and defined custom sorting logic.

Last Updated: March 3rd, 2021

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Want a remote job?

    Prepping for an interview?

    • Improve your skills by solving one coding problem every day
    • Get the solutions the next morning via email
    • Practice on actual problems asked by top companies, like:
     
     
     

    © 2013-2021 Stack Abuse. All rights reserved.