Using Lambda Expressions to Sort a Collection


Sorting lists

Prior to Java 8, it was necessary to implement the java.util.Comparator interface with an anonymous (or named) class when sorting a list1:

Version ≥ Java SE 1.2
List<Person> people = ...
Collections.sort(
	people,
	new Comparator<Person>() {
		public int compare(Person p1, Person p2){
			return p1.getFirstName().compareTo(p2.getFirstName());
		}
	}
);

Starting with Java 8, the anonymous class can be replaced with a lambda expression. Note that the types for the parameters p1 and p2 can be left out, as the compiler will infer them automatically:

Collections.sort(
     people,
     (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);

The example can be simplified by using Comparator.comparing and method references expressed using the :: (double colon) symbol

Collections.sort(
    people,
    Comparator.comparing(Person::getFirstName)
);

A static import allows us to express this more concisely, but it is debatable whether this improves overall readability:

import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));

Comparators built this way can also be chained together. For example, after comparing people by their first name, if there are people with the same first name, the thenComparing method with also compare by last name:

sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));

1 - Note that Collections.sort(...) only works on collections that are subtypes of List. The Set and Collection APIs do not imply any ordering of the elements.

Sorting maps

You can sort the entries of a HashMap by value in a similar fashion. (Note that a LinkedHashMap must be used as the target. The keys in an ordinary HashMap are unordered.)

Map<String, Integer> map = new HashMap(); // ... or any other Map class
// populate the map
map = map.entrySet()
	.stream()
	.sorted(Map.Entry.<String, Integer>comparingByValue())
	.collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
	(k, v) -> k, LinkedHashMap::new));

Method References

Method references allow predefined static or instance methods that adhere to a compatible functional interface to be passed as arguments instead of an anonymous lambda expression.

Assume that we have a model:

class Person {
	private final String name;
	private final String surname;
 
	public Person(String name, String surname){
		this.name = name;
		this.surname = surname;
	}
	public String getName(){
		return name;
	}
 
	public String getSurname(){
		return surname;
	}
}
 
List<Person> people = getSomePeople();

Instance method reference (to an arbitrary instance)

You can sort the entries of a HashMap by value in a similar fashion. (Note that a LinkedHashMap must be used as the target. The keys in an ordinary HashMap are unordered.)

people.stream().map(Person::getName)
 
The equivalent lambda:
 
people.stream().map(person -> person.getName())

In this example, a method reference to the instance method getName() of type Person, is being passed. Since it's known to be of the collection type, the method on the instance (known later) will be invoked.

Instance method reference (to a specific instance)

You can sort the entries of a HashMap by value in a similar fashion. (Note that a LinkedHashMap must be used as the target. The keys in an ordinary HashMap are unordered.)

people.forEach(System.out::println);
 
Since System.out is an instance of PrintStream, a method reference to this specific instance is being passed as an argument.
 
The equivalent lambda:
 
people.forEach(person -> System.out.println(person));

Static method reference

Also for transforming streams we can apply references to static methods:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)

This example passes a reference to the static valueOf() method on the String type. Therefore, the instance object in the collection is passed as an argument to valueOf().

The equivalent lambda:

numbers.stream().map(num -> String.valueOf(num))

Reference to a constructor

List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)

Read Collect Elements of a Stream into a Collection to see how to collect elements to collection.

The single String argument constructor of the Integer type is being used here, to construct an integer given the string provided as the argument. In this case, as long as the string represents a number, the stream will be mapped to Integers. The equivalent lambda:

strings.stream().map(s -> new Integer(s));
Method Reference Format Code Equivalent Lambda
Static method Type::method (args) -> Type.method(args)
Non-static method (on instance*) instance::method (args) -> instance.method(args)
Non-static method (no instance) TypeName::method (instance, args) -> instance.method(args)
Constructor** TypeName::new (args) -> new TypeName(args)
Array constructor TypeName[]::new (int size) -> new TypeName[size]

* instance can be any expression that evaluates to a reference to an instance, e.g. getInstance()::method, this::method

** If TypeName is a non-static inner class, constructor reference is only valid within the scope of an outer class instance

Basic Programs