Card 05/ 09

WalkthroughDifficulty: Advanced1 min

Building a Comparator That Sorts on Two Fields and Then Reverses

A report wants invoices by customer, and within each customer by amount, largest first. Written as one expression it is unreadable; built up a piece at a time it is four steps that each do one thing.

From one field to a two-field ordering with a reversal

Step 1 of 4

Order by the first field

java
Comparator<Invoice> byCustomer = Comparator.comparing(Invoice::customer);

comparing takes a method that pulls out something already comparable and builds a comparator from it. Everything with the same customer now ties, and ties are where the next step goes.

Break the ties with the second field

java
Comparator<Invoice> byCustomerThenAmount =
        byCustomer.thenComparingLong(Invoice::pence);

thenComparing is consulted only when the one before it returns zero. The order of the chain is the order of precedence, reading left to right.

Reverse the part that needs reversing

java
Comparator<Invoice> report =
        Comparator.comparing(Invoice::customer)
                  .thenComparing(Comparator.comparingLong(Invoice::pence).reversed());

This is the step that goes wrong. reversed() applies to the whole comparator it is called on, so putting it at the end of the chain would reverse the customer order too. Reversing only the amount means building that piece separately and handing it to thenComparing.

Decide what happens to nulls, if any are possible

java
Comparator<Invoice> safe =
        Comparator.comparing(Invoice::customer, Comparator.nullsLast(Comparator.naturalOrder()));

Without this, a null customer throws inside the sort rather than at the line that created the invoice. nullsFirst and nullsLast wrap an existing comparator and say where the nulls go.

The run to remember is step three. reversed() binds to everything to its left, and the fix is always to reverse the small piece rather than the chain.