Java Stream sorted operation

Introduction

In this post, let us learn about the Java Stream sorted operation. Using this, we can sort the elements of a Java Stream.

Sorting a Java Stream

In order to sort a Java stream, we can apply the sorted() operation on it. There are two overloaded sorted methods we can use. The first takes no arguments and we can use it to sort the elements of a stream which are Comparable i.e., they implement the Comparable interface. We can use the second overloaded Stream#sorted method by passing a custom Comparator to sort the elements as per the ordering imposed by the passed comparator.

Calling the sorted() operation on a stream will return a new stream with the elements of the stream sorted according to their natural order.

Sorting a Java Stream – The stream elements implement Comparable

Let us now look at how we can sort a stream of elements which are Comparable. In the below example, we have a List<String> and the String class implements Comparable<String>. We get the stream out of the list and call sorted() on the stream and print the elements in the sorted stream using forEach.

List<String> elements = List.of("pear", "apple", "fig", "orange");
elements.stream()
        .sorted()
        .forEach(System.out::println);

Since the String class’s Comparable orders based on the lexicographic ordering, the result is that the fruit names are sorted by their names.

apple
fig
orange
pear

Sorting a stream of custom type

Let us now look at an example of sorting a stream having elements of a custom class. Shown below is the Student class. Each student has an id and a name. The Student class has a constructor, getter/accessor methods for the fields and toString to print the instance variable values.

package com.javadevcentral.stream.sort;

public class Student {
    private final int id;
    private final String name;

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

We have a list of students, as shown below.

List<Student> students = List.of(
        new Student(2, "Adam"),
        new Student(1, "Scott"),
        new Student(4, "Joe"),
        new Student(3, "Chris")
);
students.stream()
        .sorted()
        .forEach(System.out::println);

If we attempt to sort the stream of students, we would get a ClassCastException (full exception message is shown below). This is because the Student class doesn’t implement the Comparable interface and hence the Student instance cannot be cast to a Comparable.

Exception in thread "main" java.lang.ClassCastException: class com.javadevcentral.stream.sort.Student cannot be cast to class java.lang.Comparable

Making the custom class implement the Comparable interface

To sort the stream of student values, we can either

  1. Make the Student class implement Comparable interface.
  2. Pass a comparator to the sorted() method.

In this section, we will implement the Comparable interface. We will look at how to pass a custom comparator later on.

To make the Student class implement the Comparable interface, we add the implements Comparable<Student> and provide an implementation for the compareTo(Student o) method. Here, we compare two Students based on their id alone.

package com.javadevcentral.stream.sort;

public class Student implements Comparable<Student> {
    private final int id;
    private final String name;

    //constructor, accessor methods and toString as before

    @Override
    public int compareTo(Student o) {
        return Integer.compare(id, o.id);
    }
}

Now, if we sort the list of Students, we get the sorted stream of student instances which are sorted based on the Student’s id value.

List<Student> students = List.of(
        new Student(2, "Adam"),
        new Student(1, "Scott"),
        new Student(4, "Joe"),
        new Student(3, "Chris")
);
students.stream()
        .sorted()
        .forEach(System.out::println);

Running this, we get,

Student{id=1, name='Scott'}
Student{id=2, name='Adam'}
Student{id=3, name='Chris'}
Student{id=4, name='Joe'}

Sorting a Java Stream using a Comparator

Now, we will see how to use the other overloaded Stream#sorted method which accepts a custom Comparator using which it will sort (order) the stream elements.

We use the same example used before, i.e., list of fruit names (List<String>). Then we use Comparator.comparingInt to build a comparator which orders the stream elements (string values) based on their length. The method reference we pass to the Comparator.comparingInt is a ToIntFunction which converts an object to a primitive int value. Here, we use it to convert each string value to its length. When written as a lambda expression, it would be string -> string.length().

In other words, for each element in the stream, it invokes the passed function to get the string’s length and since we have used Comparator.comparingInt, it orders (sorts) the stream based on the resultant integers (length of each string value).

List<String> elements = List.of("pear", "apple", "fig", "orange");
elements.stream()
        .sorted(Comparator.comparingInt(String::length))
        .forEach(System.out::println);

The result is that the fruit names are ordered from the shortest to the longest in terms of their length.

fig
pear
apple
orange

Note: You can refer to the Comparator#comparing post to learn more about the various methods in the Comparator class.

If we add reversed() to the resulting Comparator, the result will be reversed (descending order with respect to the length of the fruit names).

elements.stream()
        .sorted(Comparator.comparingInt(String::length).reversed())
        .forEach(System.out::println);

We get,

orange
apple
pear
fig

As a final example, if we use Comparator.reverseOrder(), then the stream of fruit names will be sorted in descending order of the names. Note that using Comparator.reverseOrder is only possible if the stream elements implement the Comparator interface.

elements.stream()
        .sorted(Comparator.reverseOrder())
        .forEach(System.out::println);
pear
orange
fig
apple

Sorting a stream of custom type using a comparator

Now let us return to the sorting of Stream example and use a Comparator for sorting the stream of student values.

We have the same list of Students as shown before. Now, we pass a Comparator to the Stream#sorted method to sort the stream by the Student’s name. The Function (key extractor) we pass to the Comparator.comparing method maps a Student object to the name of the Student. The resulting type of this mapping should be a Comparable (String here). Thus, for each of the student instances in the stream, the stream pipeline calls the key extractor function to map a student to a String and sorts the stream based on the String value (name of the student).

List<Student> students = List.of(
        new Student(2, "Adam"),
        new Student(1, "Scott"),
        new Student(4, "Joe"),
        new Student(3, "Chris")
);

students.stream()
        .sorted(Comparator.comparing(Student::getName))
        .forEach(System.out::println);

The result of sorting the stream of students by their name is shown below.

Student{id=2, name='Adam'}
Student{id=3, name='Chris'}
Student{id=4, name='Joe'}
Student{id=1, name='Scott'}

As seen before, if we chain reversed() to the comparator, it reverses the order.

students.stream()
        .sorted(Comparator.comparing(Student::getName).reversed())
        .forEach(System.out::println);
Student{id=1, name='Scott'}
Student{id=4, name='Joe'}
Student{id=3, name='Chris'}
Student{id=2, name='Adam'}

A word on sort stability

A sort is said to be stable when it retains the equal elements in the same order as they appear in the input. As per the Javadoc of Stream#sorted, for ordered streams, the sort is stable and for unordered streams, no stability guarantees are made.

But when we apply the sorted() operation on a parallel ordered stream, there is a bug (open as of writing this) which causes it to be non-stable in some cases and hence it violates the contract.

Conclusion

In this post, we learnt about the Java Stream sorted operation. We first saw how to use it on a stream of Comparable elements. Then we used the overloaded sorted() method by passing a custom comparator.

Leave a Reply