Comparators in Depth

Streams · lesson 31 of 42 · 4 min read

Sort by one key, break ties with another, reverse it, and survive a null.

Open this lesson in the learning hub

Key points

  • Comparator.comparing(Track::artist) builds a comparator from a key extractor. That is most sorting.
  • Chain tie-breakers with thenComparing. Each one only runs when everything before it compared equal.
  • reversed() flips everything to its left, so a.thenComparing(b).reversed() reverses both keys.
  • Use comparingInt, comparingLong or comparingDouble for primitive keys: no boxing per compare.
  • nullsFirst and nullsLast wrap a comparator so a null sorts instead of throwing.
  • sorted() is stable: equal elements keep their original order, which is what makes chaining work.

Example

import java.util.*;
import java.util.stream.*;

public class Main {
    record Track(String artist, String title, int seconds) {}

    public static void main(String[] args) {
        List<Track> tracks = List.of(
                new Track("Ada", "Zeta", 210),
                new Track("Ada", "Alpha", 185),
                new Track("Bo", "Mu", 185),
                new Track("Bo", "Nu", 320));

        Comparator<Track> byArtist = Comparator.comparing(Track::artist);
        Comparator<Track> byLength = Comparator.comparingInt(Track::seconds);

        show("artist, then title  ", tracks, byArtist.thenComparing(Track::title));
        show("longest first       ", tracks, byLength.reversed());
        show("reversed BOTH keys  ", tracks, byArtist.thenComparing(Track::title).reversed());
        show("artist, longest first", tracks, byArtist.thenComparing(byLength.reversed()));

        // A null blows up the sort unless the comparator is wrapped.
        List<String> withNull = Arrays.asList("pear", null, "fig");
        System.out.println("nullsFirst  : " + withNull.stream()
                .sorted(Comparator.nullsFirst(Comparator.naturalOrder())).toList());
        try {
            withNull.stream().sorted(Comparator.naturalOrder()).toList();
        } catch (NullPointerException e) {
            System.out.println("plain sort  : NullPointerException");
        }
    }

    static void show(String label, List<Track> tracks, Comparator<Track> c) {
        System.out.println(label + ": " + tracks.stream().sorted(c)
                .map(Track::title).collect(Collectors.joining(", ")));
    }
}

comparing, then thenComparing, and remember reversed() flips the whole chain before it.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.