Java 8 – Powerful
Comparison with Lambdas
Last modi몭ed: August 15, 2020
by baeldung
Java +
Java 8
Get started with Spring 5 and Spring Boot 2, through the
Learn Spring course:
>> CHECK OUT THE COURSE
1. Overview
In this tutorial, we're going to take a 몭rst look at the Lambda support in Java
8 – speci몭cally at how to leverage it to write the Comparator and sort a
Collection.
This article is part of the “Java – Back to Basic” series here on Baeldung.
Further reading:
The Java 8 Stream API Tutorial
The article is an example-heavy introduction of the possibilities and
operations o몭ered by the Java 8 Stream API.
Read more →
Guide to Java 8's Collectors
The article discusses Java 8 Collectors, showing examples of built-in
collectors, as well as showing how to build custom collector.
Read more →
Lambda Expressions and Functional Interfaces: Tips
and Best Practices
Tips and best practices on using Java 8 lambdas and functional interfaces.
Read more →
First, let's de몭ne a simple entity class:
public class Human {
private String name;
private int age;
// standard constructors, getters/setters, equals and hashcode
}
2. Basic Sort Without Lambdas
Before Java 8, sorting a collection would involve creating an anonymous
inner class for the Comparator used in the sort:
new Comparator<Human>() {
@Override
public int compare(Human h1, Human h2) {
return h1.getName().compareTo(h2.getName());
}
}
This would simply be used to sort the List of Human entities:
@Test
public void
givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Collections.sort(humans, new Comparator<Human>() {
@Override
public int compare(Human h1, Human h2) {
return h1.getName().compareTo(h2.getName());
}
});
Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
3. Basic Sort With Lambda Support
With the introduction of Lambdas, we can now bypass the anonymous inner
class and achieve the same result with simple, functional semantics:
(final Human h1, final Human h2) ->
h1.getName().compareTo(h2.getName());
Similarly – we can now test the behavior just as before:
@Test
public void whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
humans.sort(
(Human h1, Human h2) -> h1.getName().compareTo(h2.getName()));
assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
Notice that we're also using the new sort API added to java.util.List in Java
8 – instead of the old Collections.sort API.
4. Basic Sorting With No Type De몭nitions
We can further simplify the expression by not specifying the type de몭nitions
– the compiler is capable of inferring these on its own:
(h1, h2) -> h1.getName().compareTo(h2.getName())
And again, the test remains very similar:
@Test
public void
givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
5. Sort Using Reference to Static Method
Next, we're going to perform the sort using a Lambda Expression with a
reference to a static method.
First, we're going to de몭ne the method compareByNameThenAge – with the
exact same signature as the compare method in a Comparator<Human>
object:
public static int compareByNameThenAge(Human lhs, Human rhs) {
if (lhs.name.equals(rhs.name)) {
return Integer.compare(lhs.age, rhs.age);
} else {
return lhs.name.compareTo(rhs.name);
}
}
Now, we're going to call the humans.sort method with this reference:
humans.sort(Human::compareByNameThenAge);
The end result is a working sorting of the collection using the static method
as a Comparator:
@Test
public void
givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySort
ed() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
humans.sort(Human::compareByNameThenAge);
Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
6. Sort Extracted Comparators
We can also avoid de몭ning even the comparison logic itself by using an
instance method reference and the Comparator.comparing method –
which extracts and creates a Comparable based on that function.
We're going to use the getter getName() to build the Lambda expression
and sort the list by name:
@Test
public void
givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Collections.sort(
humans, Comparator.comparing(Human::getName));
assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
7. Reverse Sort
JDK 8 has also introduced a helper method for reversing the comparator –
we can make quick use of that to reverse our sort:
@Test
public void whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Comparator<Human> comparator
= (h1, h2) -> h1.getName().compareTo(h2.getName());
humans.sort(comparator.reversed());
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
8. Sort With Multiple Conditions
The comparison lambda expressions need not be this simple – we can write
more complex expressions as well – for example sorting the entities 몭rst by
name, and then by age:
@Test
public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 12),
new Human("Sarah", 10),
new Human("Zack", 12)
);
humans.sort((lhs, rhs) -> {
if (lhs.getName().equals(rhs.getName())) {
return Integer.compare(lhs.getAge(), rhs.getAge());
} else {
return lhs.getName().compareTo(rhs.getName());
}
});
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
9. Sort With Multiple Conditions –
Composition
The same comparison logic – 몭rst sorting by name and then, secondarily, by
age – can also be implemented by the new composition support for
Comparator.
Starting with JDK 8, we can now chain together multiple comparators to
build more complex comparison logic:
@Test
public void
givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted()
{
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 12),
new Human("Sarah", 10),
new Human("Zack", 12)
);
humans.sort(
Comparator.comparing(Human::getName).thenComparing(Human::getAge)
);
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
10. Sorting a List With Stream.sorted()
We can also sort a collection using Java 8's Stream sorted() API.
We can sort the stream using natural ordering as well as ordering provided
by a Comparator. For this, we have two overloaded variants of the sorted()
API:
sorted() – sorts the elements of a Stream using natural ordering; the
element class must implement the Comparable interface.
sorted(Comparator<? super T> comparator) – sorts the elements based
on a Comparator instance
Let's see an example of how to use the sorted() method with natural
ordering:
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted
() {
List<String> letters = Lists.newArrayList("B", "A", "C");
List<String> sortedLetters =
letters.stream().sorted().collect(Collectors.toList());
assertThat(sortedLetters.get(0), equalTo("A"));
}
Now let's see how we can use a custom Comparator with the sorted() API:
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted(
){
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new
Human("Jack", 12));
Comparator<Human> nameComparator = (h1, h2) ->
h1.getName().compareTo(h2.getName());
List<Human> sortedHumans =
humans.stream().sorted(nameComparator).collect(Collectors.toList());
assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}
We can simplify the above example even further if we use the
Comparator.comparing() method:
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySor
ted() {
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new
Human("Jack", 12));
List<Human> sortedHumans = humans.stream()
.sorted(Comparator.comparing(Human::getName))
.collect(Collectors.toList());
assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}
11. Sorting a List in Reverse With
Stream.sorted()
We can also use Stream.sorted() to sort a collection in reverse.
First, let's see an example of how to combine the sorted() method with
Comparator.reverseOrder() to sort a list in the reverse natural order:
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrect
givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrect
lySorted() {
List<String> letters = Lists.newArrayList("B", "A", "C");
List<String> reverseSortedLetters = letters.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
assertThat(reverseSortedLetters.get(0), equalTo("C"));
}
Now, let's see how we can use the sorted() method and a custom
Comparator:
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectl
ySorted() {
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new
Human("Jack", 12));
Comparator<Human> reverseNameComparator =
(h1, h2) -> h2.getName().compareTo(h1.getName());
List<Human> reverseSortedHumans =
humans.stream().sorted(reverseNameComparator)
.collect(Collectors.toList());
assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah",
10)));
}
Note that the invocation of compareTo is 몭ipped, which is what is doing the
reversing.
Finally, let's simplify the above example by using the
Comparator.comparing() method:
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorr
ectlySorted() {
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new
Human("Jack", 12));
List<Human> reverseSortedHumans = humans.stream()
.sorted(Comparator.comparing(Human::getName,
Comparator.reverseOrder()))
.collect(Collectors.toList());
assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah",
10)));
}
12. Null Values
So far, we implemented our Comparators in a way that they can't sort
collections containing null values. That is, if the collection contains at least
one null element, then the sort method throws a NullPointerException:
@Test(expected = NullPointerException.class)
public void givenANullElement_whenSortingEntitiesByName_thenThrowsNPE()
{
List<Human> humans = Lists.newArrayList(null, new Human("Jack",
12));
humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
}
The simplest solution is to handle the null values manually in
our Comparator implementation:
@Test
public void
givenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLa
st() {
List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12),
null);
humans.sort((h1, h2) -> {
if (h1 == null) {
return h2 == null ? 0 : 1;
}
else if (h2 == null) {
return -1;
}
return h1.getName().compareTo(h2.getName());
});
Assert.assertNotNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNull(humans.get(2));
}
Here we're pushing all null elements towards the end of the collection. To
do that, the comparator considers null to be greater than non-null values.
When both are null, they are considered equal.
Additionally, we can pass any Comparator that is not null-safe into the
Comparator.nullsLast() method and achieve the same result:
@Test
public void
givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast() {
List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12),
null);
humans.sort(Comparator.nullsLast(Comparator.comparing(Human::getName)));
Assert.assertNotNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNull(humans.get(2));
}
Similarly, we can use Comparator.nullsFirst() to move the null elements
towards the start of the collection:
@Test
public void
givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart() {
List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12),
null);
humans.sort(Comparator.nullsFirst(Comparator.comparing(Human::getName)))
;
Assert.assertNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNotNull(humans.get(2));
}
It's highly recommended to use the nullsFirst() or nullsLast() decorators,
as they're more 몭exible and, above all, more readable.
13. Conclusion
This article illustrated the various and exciting ways that a List can be
sorted using Java 8 Lambda Expressions – moving right past syntactic
sugar and into real and powerful functional semantics.
The implementation of all these examples and code snippets can be found
over on GitHub.
Get started with Spring 5 and Spring Boot 2,
through the Learn Spring course:
>> CHECK OUT THE COURSE
Learning to "Build your API
with Spring"?
Enter your email address
>> Get the eBook
12 COMMENTS Oldest
View Comments
Comments are closed on this article!
CATEGORIES
SPRING
REST
JAVA
SECURITY
PERSISTENCE
JACKSON
HTTP CLIENT-SIDE
SERIES
JAVA “BACK TO BASICS” TUTORIAL
JACKSON JSON TUTORIAL
HTTPCLIENT 4 TUTORIAL
REST WITH SPRING TUTORIAL
SPRING PERSISTENCE TUTORIAL
SECURITY WITH SPRING
SPRING REACTIVE TUTORIALS
ABOUT
ABOUT BAELDUNG
THE COURSES
JOBS
THE FULL ARCHIVE
WRITE FOR BAELDUNG
EDITORS
OUR PARTNERS
ADVERTISE ON BAELDUNG
TERMS OF SERVICE
PRIVACY POLICY
COMPANY INFO
CONTACT