Card 05/ 09
Building a Comparator on Two Fields With One Reversed
By customer, then by amount largest first, and where reversed() has to go.
Comparator.comparing builds a comparator from one field, thenComparing adds a tie-breaker, and reversed() flips an order. Combining them sorts on several fields at once, with one catch: reversed() applies to everything before it in the chain.
A report wants invoices by customer, and within each customer by amount, largest first. Built up one piece at a time, it is four steps that each do one thing. You need an Invoice with customer() and pence() accessors. thenComparingLong and comparingLong are the variants for a long field, which compare the numbers directly instead of wrapping each one in an object first.
From one field to a two-field ordering with a reversal
Order by the first field
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
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 only the part that needs reversing
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 adding it at the end of the chain would reverse the customer order too. To reverse only the amount, build that piece separately, reverse it, and hand it to thenComparing.
Decide where nulls go, if any are possible
Comparator<Invoice> safe =
Comparator.comparing(Invoice::customer, Comparator.nullsLast(Comparator.naturalOrder()));Without this, a null customer throws inside the sort rather than where the invoice was created. nullsFirst and nullsLast wrap an existing comparator and say where nulls go.
You know the comparator is right when the report shows customers A to Z, and each customer's largest invoice first. The step to remember is the third: reverse the small piece, never the whole chain that comes after it, whatever its length.
← → move through sections and cards