λ Lambdas & Functional Java · Intermediate

Comparator with lambdas in Java

Comparator.comparing, nullsFirst, chaining.

🧩 The mysteryComparator.comparing(p -> p.name()).reversed() refuses to compile, yet Comparator.comparing(P::name).reversed() works fine. Let's master Comparators, and solve that mystery.

Sort rules as one-liners

**Comparator.comparing(keyExtractor) builds a comparator from a function that extracts the sort key, then compares keys by natural order. comparingInt avoids boxing. thenComparing breaks ties, and reversed()** flips the order.

people.sort(Comparator
    .comparing(Person::city)
    .thenComparingInt(Person::age)
    .reversed());
🔮 Predict it

Your turn

What does this print?

List<String> words = new ArrayList<>(
    List.of("pear", "kiwi", "banana", "fig"));
words.sort(Comparator.comparing(String::length)
    .thenComparing(Comparator.reverseOrder()));
System.out.println(words);
  1. [fig, kiwi, pear, banana]
  2. [fig, pear, kiwi, banana]
  3. [banana, pear, kiwi, fig]
Show the answer

Shortest first: fig (3). Then the 4-letter tie, broken in reverse alphabetical order: pear before kiwi. Then banana (6).

Nulls crash natural order

**Comparator.naturalOrder()** calls compareTo on the elements. If one of them is null, calling a method on it throws **NullPointerException**.

// compares a.compareTo(b)
// a == null → NullPointerException
🔮 Predict it

A null in the list

What happens?

List<String> names = new ArrayList<>(
    Arrays.asList("cy", null, "ab"));
names.sort(Comparator.naturalOrder());
System.out.println(names);
  1. [null, ab, cy]
  2. [ab, cy, null]
  3. Throws NullPointerException
Show the answer

Sooner or later, natural order compares against the null element and calls a method on it: **NullPointerException**.

nullsFirst / nullsLast

**Comparator.nullsFirst(cmp) and nullsLast(cmp) wrap another comparator. They handle nulls themselves** and only pass non-null pairs to the wrapped comparator.

names.sort(Comparator.nullsFirst(
    Comparator.naturalOrder()));
// [null, ab, cy]
🔮 Predict it

Nulls to the back

What does this print?

List<String> names = new ArrayList<>(
    Arrays.asList("cy", null, "ab"));
names.sort(Comparator.nullsLast(
    Comparator.naturalOrder()));
System.out.println(names);
  1. [null, ab, cy]
  2. [ab, cy, null]
  3. Throws NullPointerException
Show the answer

nullsLast puts the null at the end and sorts the rest naturally: [ab, cy, null].

⚠️ The trap

Lambdas lose their type before .reversed()

Type inference doesn't flow back through a chained call. In comparing(p -> p.name()).reversed(), comparing can't see its target type, so p becomes **Object** and p.name() doesn't compile. Use a method reference or an explicit parameter type.

comparing(p -> p.name()).reversed();     // ✗
comparing(P::name).reversed();           // ✓
comparing((P p) -> p.name()).reversed(); // ✓
💼 In the real world

Every sortable screen

Product lists by price then rating, inboxes newest first, user tables with missing last names: real data is messy and often contains nulls. comparing, thenComparing, reversed and nullsLast handle it in one readable line, with no hand-written compare method.

Key takeaways

  1. comparing(Person::name), comparingInt(Person::age) avoids boxing
  2. thenComparing(...) breaks ties
  3. nullsFirst(naturalOrder()) puts nulls at the front
  4. Lambdas lose type inference before .reversed() — prefer method refs
🤯 Did you know?

Comparator.comparing, thenComparing, nullsFirst and nullsLast all arrived in Java 8 as static and default methods. Before that, every multi-key sort meant a hand-written compare method full of if statements.

Practice questions

What does this print?

List<String> words = new ArrayList<>(
    List.of("kiwi", "fig", "apple", "date"));
words.sort(Comparator.comparing(String::length)
    .thenComparing(Comparator.reverseOrder()));
System.out.println(words);
  1. [fig, date, kiwi, apple]
  2. [fig, kiwi, date, apple]
  3. [apple, kiwi, date, fig]
  4. [fig, kiwi, apple, date]
Check your answer

[fig, kiwi, date, apple]. Shortest first: fig (3), then the 4-letter tie kiwi/date in reverse alphabetical order (kiwi before date), then apple (5).

What happens when this runs?

List<String> names = new ArrayList<>(
    Arrays.asList("bo", null, "al"));
names.sort(Comparator.naturalOrder());
System.out.println(names);
  1. [null, al, bo]
  2. [al, bo, null]
  3. Throws NullPointerException
  4. [al, bo]
Check your answer

Throws NullPointerException. naturalOrder calls compareTo on the elements, and calling a method on null throws NullPointerException.

Next world: Streams! What if you could filter, transform and sum a million orders in one readable pipeline, without writing a single loop?