4.5.  Sort a collection using lambda expressions

[Note]

Sorting a Stream

Java streams API has several sorting methods.

Below example shows the stream of employees is mapped to the employee names and then there is a sorted() method, which returns the sorted stream of employee names. Remember, the Stream.sorted() is a stateful intermediate operation which does not take any parameter here, and hence it will sort the list in natural order.

public class Employee {
    public String name;

    public Employee(String n) {
        name = n;
    }
}
					


Stream<Employee> emps = Stream.of(new Employee("John"), new Employee("Jane"), new Employee("Jack"));
List<String> sl = emps
    .map(e -> e.name)
    .sorted()
    .collect(Collectors.toList());
System.out.print(sl);

					

Output:

[Jack, Jane, John]
					

[Important]

The Stream.sorted() method without parameters requires a stream of Comparable elements!

If the elements of this stream are not Comparable, a java.lang.ClassCastException may be thrown when the terminal operation is executed.

For ordered streams, the sort is stable. For unordered streams, no stability guarantees are made.

We can use our own sorting logic by calling Stream.sorted(...) intermediate operation and passing a Comparator interface as a parameter. The comparing(...) and few other useful static and default methods have been added to the java.util.Comparator interface in Java 8.0 to simplify these scenarios.

Here is an example how the Comparator.comparing(...) method is used to provide a custom sorting logic (e.g. when elements do not implement java.lang.Comparable interface or when the same elements need to be sorted by different algorithms).

public class Employee {
    public String name;

    public Employee(String n) {
        name = n;
    }
    
    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name;
    }
}
					


Stream<Employee> emps = Stream.of(new Employee("Nathaniel"), new Employee("Steve"), new Employee("Nick"));
List<Employee> sl = emps
    .sorted(Comparator.comparing(e -> e.name.length()))
    .collect(Collectors.toList());
System.out.print(sl);

					

The output of the above code is employee list in sorted by name length order:

[Nick, Steve, Nathaniel]
					

Comparator.thenComparing(...) is a default method of the Comparator interface introduced in Java 8.0. If we have two Comparator interface instances, and we want to do a sorting by composite condition (by first comparator and then by second comparator), we can use both comparators invoking thenComparing(...) on fist instance and passing in the second instance. Find the example below:


Stream<Employee> emps = Stream.of(
    new Employee("Nathaniel"), new Employee("Jane"),
    new Employee("Steve"), new Employee("Nick"),
    new Employee("Jack"));

Comparator<Employee> c1 = Comparator.comparing(e -> e.name.length());
Comparator<Employee> c2 = (e1, e2) -> e1.name.compareTo(e2.name);

List<Employee> sl = emps
    .sorted(c1.thenComparing(c2))
    .collect(Collectors.toList());
System.out.println(sl);

					

The output of the above code is employee list in sorted by name length order, and in case name lengths are equal, such employees are sorted by name alphabetical order:

[Jack, Jane, Nick, Steve, Nathaniel]
					

You can sort a stream in the reverse order by preparing a simple Comparator instance for the sort and then calling reversed() method on it to get the reversed version of that Comparator:


Comparator<Employee> byNameLengthDesc = Comparator.comparing((Employee e) -> e.name.length()).reversed();
Stream<Employee> emps = Stream.of(new Employee("Nathaniel"), new Employee("Steve"), new Employee("Nick"));
List<Employee> sl = emps
    .sorted(byNameLengthDesc)
    .collect(Collectors.toList());
System.out.print(sl);
					
					

[Nathaniel, Steve, Nick]
					

Sorting a List

The java.util.Collections.sort(...) method sorts the specified List into ascending order, according to the natural ordering of its elements. All elements in the list must implement the Comparable interface. Furthermore, all elements in the list must be mutually comparable:


List<String> emps = new ArrayList<>();
emps.add("Nathaniel"); emps.add("Steve"); emps.add("Nick");
Collections.sort(emps);
System.out.print(emps);
					
					

[Nathaniel, Nick, Steve]
					

[Warning]

If you initialize a list like this, it will fail at runtime, as the factory method creates immutable list object and sorting modifies exactly the same list object:


List<String> emps = List.of("Nathaniel", "Steve","Nick");
						
						

If the objects in the list do not implement Comparable, you should provide your custom Comparator:


List<Employee> emps = new ArrayList<>();        
emps.add(new Employee("Nathaniel"));
emps.add(new Employee("Steve")); 
emps.add(new Employee("Nick"));
Comparator<Employee> c = (e1, e2) -> e1.name.compareTo(e2.name);
Collections.sort(emps, c);
System.out.print(emps);
					
					

[Nathaniel, Nick, Steve]
					

Or the same logic by using Comparator.comparing(...) method:


Comparator<Employee> c = Comparator.comparing(e -> e.name);

					

Or using method reference syntax (assume Employee has String getName() method which returns name):


Comparator<Employee> c = Comparator.comparing(Employee::getName);

					

As of Java 8.0 the java.util.List interface has default void sort(Comparator<? super E> c) method which can sort own elements:


List<Employee> emps = new ArrayList<>();        
emps.add(new Employee("Nathaniel"));
emps.add(new Employee("Steve")); 
emps.add(new Employee("Nick"));
Comparator<Employee> c = Comparator.comparing(Employee::getName);
emps.sort(c);
System.out.print(emps);
					
					

[Nathaniel, Nick, Steve]
					

Professional hosting         Exam 1Z0-817: Upgrade OCP Java 6, 7 & 8 to Java SE 11 Developer Quiz     Exam 1Z0-810: Upgrade to Java SE 8 Programmer Quiz