← Skill tree CS Skill Tree 0 CSCD211

reversed, nullsFirst, and nullsLast

Textbook: BJP (Reges and Stepp)

Before You Start

Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.

Not sure? Take the 60-second self-check.

1. Method return type. Comparator.comparing(Player::getTeam) returns a value of what type?

Check

Comparator<Player>. It is a value, not a sorted result. You can call further methods on it before passing it to a sort.

2. Null element NPE. Comparator.comparing(Player::getName) is called with a null player. What happens?

Check

NullPointerException. The key extractor Player::getName calls getName() on the argument. When the argument is null, there is no object to call it on, and NPE is thrown.

3. naturalOrder(). What does Comparator.naturalOrder() return?

Check

A Comparator<T extends Comparable<T>> that delegates to compareTo. It imposes the natural order of the element type.


Try This First

You have a Comparator<Player> that sorts players by games played ascending. Before reading further, write what you think the call looks like to sort descending.

Check
Comparator<Player> descending = Comparator.comparingInt(Player::getGamesPlayed).reversed();

reversed() produces a new comparator that imposes the opposite ordering.


What You Need To Walk In With

The Insight: reversed() does not reverse an already-sorted array or list. It produces a new Comparator value that imposes the opposite ordering. nullsFirst(cmp) and nullsLast(cmp) produce new Comparator values that handle null elements at a defined position. These are decorators: they wrap an existing comparator and extend its behavior, producing a new comparator value without modifying the original.

One concrete way to read reversed() is to think of it as “flip the sign”: if the original comparator returns a negative integer for the pair (a, b), the reversed one returns a positive integer for that same pair. That sign-flip is the entire mechanism, and it is why a reversed comparator is a new value rather than a modified one.

You can: produce a descending comparator with a single chained call; wrap a comparator with nullsFirst or nullsLast to sort collections that may contain null references; place the null-handling wrapper at the correct level for both null objects and null field values; and predict where null elements will appear in the sorted output.


How It Works

reversed()

reversed() is a default method on Comparator<T>. It returns a new Comparator<T> that imposes the reverse ordering of the receiver:

Comparator<Player> ascending  = Comparator.comparingInt(Player::getGamesPlayed);
Comparator<Player> descending = ascending.reversed();

// descending is a new Comparator; ascending is unchanged
Arrays.sort(players, descending);   // most games played first

The reversed comparator simply swaps the arguments on each compare call: reversed.compare(a, b) is equivalent to original.compare(b, a).

Chain form:

Arrays.sort(players, Comparator.comparingInt(Player::getGamesPlayed).reversed());

nullsFirst and nullsLast

Comparator.nullsFirst(cmp) returns a new Comparator<T> that:

Comparator.nullsLast(cmp) is the mirror: nulls after all non-null elements.

// Null players last, others by name ascending
Comparator<Player> safe =
    Comparator.nullsLast(Comparator.comparing(Player::getName));

Arrays.sort(players, safe);   // null players appear at the end

Without the wrapper, the call to Player::getName on a null player would throw NullPointerException.

Placing the wrapper correctly

The wrapper intercepts null objects at the level it is applied. Two different scenarios:

// Null Player objects last:
Comparator<Player> c1 = Comparator.nullsLast(Comparator.comparing(Player::getName));

// Null names (Player exists but Player.getName() returns null) last:
Comparator<Player> c2 = Comparator.comparing(
    Player::getName, Comparator.nullsLast(Comparator.naturalOrder()));

c1 catches null Player references before calling any method on them. c2 catches null String names returned by getName() but does not protect against a null Player reference.

To handle both, combine the two:

Comparator<Player> c3 = Comparator.nullsLast(
    Comparator.comparing(Player::getName, Comparator.nullsLast(Comparator.naturalOrder())));

Quick check

Check your understanding

After calling .reversed() on a Comparator, what is the state of the original comparator?

Tier 1 · BJP (Reges and Stepp), Ch 10


Worked Example: Predict Then Check

Player[] players = { p_Diggs, p_null, p_Allen, p_Brady };
// p_null is a null Player reference

Comparator<Player> cmp = Comparator.nullsLast(
    Comparator.comparing(Player::getName));

Arrays.sort(players, cmp);
System.out.println(Arrays.toString(players));

Player names: Allen, Brady, Diggs. Predict the order.

Reasoning

nullsLast places the null reference after all non-null players. The non-null players are sorted by name (Allen, Brady, Diggs) by Comparator.comparing(Player::getName).

Show answer

Output: [Allen, Brady, Diggs, null]

The null reference is at the end. Non-null players are in ascending name order.


Common Misconceptions

Misconception 1: reversed() reverses the sorted array

Wrong mental model: “Calling Arrays.sort(arr, cmp.reversed()) first sorts ascending, then reverses the array.”

Why it breaks: cmp.reversed() produces a new comparator before the sort begins. Only one sort occurs: a descending sort. No existing array is reversed; the sort uses the reversed comparator from the start.

If you need to reverse an already-sorted List, use Collections.reverse(list). For an array, reverse the elements manually. reversed() is the wrong tool for that job.

How to correct: When descending order is needed, build a descending comparator and pass it to Arrays.sort or list.sort. Use Collections.reverse only when you have an already-sorted collection and need to flip it.


Misconception 2: applying nullsLast at the wrong level in a chain

Wrong mental model: “I have byName.thenComparing(byCity). I’ll wrap with Comparator.nullsLast(byName.thenComparing(byCity)) to make everything null-safe.”

Why it breaks: The outer nullsLast intercepts null Person references. But if a Person object exists and its getName() method returns null, the inner Comparator.comparing(Person::getName) still calls getName().compareTo(...) and throws NPE on the null name before the outer wrapper sees anything.

How to correct: Apply null handling at the level where nulls appear. Null Person objects need an outer wrapper. Null name String values need a wrapper on the key comparator inside comparing:

Comparator<Person> safe = Comparator.nullsLast(
    Comparator.comparing(Person::getName,
                         Comparator.nullsLast(Comparator.naturalOrder())));

Source: Java 25 Comparator.nullsFirst javadoc.

Quick check

Check your understanding

A developer writes: Arrays.sort(arr, cmp.reversed()). How many sort passes occur on arr?

Tier 2 · BJP (Reges and Stepp), Ch 10


Formal Definition and Interface Contract

From the Java 25 Comparator API:

default Comparator<T> reversed()

Returns a comparator that imposes the reverse ordering of this comparator.

From the Java 25 Comparator.nullsLast API:

static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)

Returns a null-friendly comparator that considers null to be greater than non-null. When both are null, they are considered equal. If both are non-null, the specified Comparator is used to determine the order. If the specified comparator is null, then the returned comparator considers all non-null values to be equal.


Mental Model

Think of reversed(), nullsFirst(), and nullsLast() as envelope wrappers. You hand them an existing comparator; they hand you a new one with slightly different behavior. The original comparator is unchanged. You can chain wrappers: nullsLast(comparing(Person::getName).reversed()) reads as “null persons last; among non-null, sort by name in reverse alphabetical order.”


Connections

Within CSCD 210/211: The earlier Arrays.sort with a Comparator lesson mentioned Comparator.nullsLast as the fix for sorting arrays with null elements. This lesson gives the full contract for that fix.

Looking back: Comparator.comparing by Key established Comparator.comparing. This lesson adds decorators that modify the comparator’s behavior after construction.

Looking ahead: comparingInt, comparingDouble: Primitive-Friendly Variants covers comparingInt, comparingLong, and comparingDouble, which produce comparators that chain naturally with reversed() and thenComparing.


Go Deeper (optional)

None of this depth is required to write correct sorting code; the sections above are enough. These connections are here for when you find yourself curious about where the ideas come from.

The decorator pattern. The wrapping behavior of reversed(), nullsFirst(), and nullsLast() is an instance of the Decorator structural design pattern: a wrapper object that adds behavior at the edges of a method call without modifying the wrapped object. The wrapped comparator is unchanged; the decorator intercepts compare(a, b), applies its own logic (swap arguments, or intercept nulls), and then delegates to the inner comparator for the rest. Recognizing the Decorator pattern in the API makes it easier to predict what any new wrapper will and will not affect.

reverseOrder() vs .reversed(). There is a distinction worth knowing between the static factory Comparator.reverseOrder() and the instance method .reversed(). reverseOrder() produces a comparator that is the reverse of natural order directly, without needing an intermediate comparator to wrap. It is equivalent to Comparator.naturalOrder().reversed() but is produced without the extra allocation. Use reverseOrder() when you want descending natural order for a type; use .reversed() when you already have a custom or chained comparator and want to flip it.

Career context. The nullsFirst and nullsLast wrappers have a direct counterpart in SQL: the ORDER BY column NULLS LAST clause. Any Java application layer that sorts results fetched from a database, or that mirrors a SQL sort order in memory, reaches for exactly these wrappers. Understanding that null values require explicit placement (rather than undefined behavior) is a practical expectation in data-layer code.


Practice

Level 1

Write a comparator that sorts Player[] by games played descending using one chained expression.

Show answer
Arrays.sort(players, Comparator.comparingInt(Player::getGamesPlayed).reversed());

Level 2

An array of String values contains some null entries. Write the sort call that places non-null strings first (in natural lexicographic order) and all null entries at the end.

Show answer
Arrays.sort(strings, Comparator.nullsLast(Comparator.naturalOrder()));

Comparator.naturalOrder() provides lexicographic order for non-null String values. nullsLast places null entries after all non-null strings.


Level 3

A Person class has String getName() (may return null). An array of Person objects also contains some null references. Write a comparator that: (a) places null Person references last, (b) among non-null persons, places those with null names last, (c) among non-null persons with non-null names, sorts by name in reverse alphabetical order.

Show answer
Comparator<Person> c = Comparator.nullsLast(
    Comparator.comparing(
        Person::getName,
        Comparator.nullsLast(Comparator.reverseOrder())));

Reading inside-out:

  1. Comparator.reverseOrder(): reverse natural order for non-null names.
  2. Comparator.nullsLast(...): null names come after non-null names.
  3. Comparator.comparing(Person::getName, ...): extract the name and use the wrapped key comparator.
  4. Outer Comparator.nullsLast(...): null Person references come after all non-null persons.

Check Yourself

Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.

Check your understanding

What does calling .reversed() on a Comparator return?

Tier 1 · BJP (Reges and Stepp), Ch 10

An array of Player objects contains some null references. Which call places null Player references at the end and sorts non-null players by name ascending?

Tier 1 · BJP (Reges and Stepp), Ch 10

Predict the printed output. Player[] players = { pDiggs, null, pAllen, pBrady }; // pAllen.getName()=”Allen”, pBrady.getName()=”Brady”, pDiggs.getName()=”Diggs” Comparator<Player> cmp = Comparator.nullsFirst(Comparator.comparing(Player::getName).reversed()); Arrays.sort(players, cmp); System.out.println(Arrays.toString(players));

Tier 2 · BJP (Reges and Stepp), Ch 10

A Person object has a getName() method that may return null. Which comparator correctly handles both null Person references (placed last) and null names returned by getName() (also placed last), sorting non-null names in ascending order?

Tier 3 · BJP (Reges and Stepp), Ch 10

What is the difference between Comparator.reverseOrder() and the instance method .reversed()?

Tier 2 · BJP (Reges and Stepp), Ch 10