How does Comparator.comparing with a key extractor work, and why is it preferred over writing compare by hand?
answer
- comparing(keyExtractor) -> compares by the extracted key's natural order
- second arg = a Comparator for the key (custom key order)
- comparingInt/Long/Double avoid autoboxing + overflow
- extractor runs per comparison (O(n log n) times)
- returns a Comparator -> chainable with thenComparing/reversed
basics
~20 sComparator.comparing takes a function that pulls a sort key out of each object (like person -> person.getAge()), and builds a comparator that orders objects by that key. It's shorter and less error-prone than writing compare yourself.
solid answer
~40 sComparator.comparing is a factory: you give it a *key extractor* - a function mapping each element to a Comparable sort key (e.g. Person::getLastName) - and it returns a Comparator that compares two elements by comparing their extracted keys using the key's natural order. You can pass a second argument, a Comparator for the key, to order by a non-natural key order. For primitives there are specialized variants - comparingInt, comparingLong, comparingDouble - that avoid autoboxing and overflow bugs. It's preferred over a hand-written compare because it's declarative, composable (you can chain thenComparing, reversed, nullsFirst), reads like the intent ('compare by last name'), and reuses correct, contract-respecting comparisons of the key type instead of risking subtraction overflow or sign mistakes.
go deeper
Can use Comparator.comparing with a method reference to sort by one field.
Chooses comparingInt/Long/Double for primitive keys, passes a key comparator for custom key order, and knows the result is chainable.
Explains that the extractor runs per comparison, the boxing/overflow advantages of the primitive variants, and when to precompute keys.
Weighs decorate-sort-undecorate vs comparing on hot paths, and designs comparator factories for reuse across a codebase.
## Building comparators declaratively Writing `compare` by hand is repetitive and bug-prone. Java 8 added factory methods that build comparators from a **key extractor** - a function that, given an element, returns the value to sort by. ### The basic form ```java Comparator<Person> byLastName = Comparator.comparing(Person::getLastName); people.sort(byLastName); ``` Here `Person::getLastName` is the key extractor (a `Function<Person, String>`). `comparing` returns a `Comparator<Person>` whose `compare(p1, p2)` effectively does: ```java return p1.getLastName().compareTo(p2.getLastName()); ``` It extracts the key from each element and compares the **keys** using the key type's natural ordering (here `String`'s). The extracted key must itself be `Comparable`. ### Supplying a custom key ordering If the key shouldn't use natural order, pass a second argument - a `Comparator` for the key: ```java Comparator<Person> byLastNameCaseInsensitive = Comparator.comparing(Person::getLastName, String.CASE_INSENSITIVE_ORDER); ``` Now the keys are compared with the given key-comparator instead of `compareTo`. ### Primitive specializations: comparingInt/Long/Double ```java Comparator<Person> byAge = Comparator.comparingInt(Person::getAge); ``` If you wrote `Comparator.comparing(Person::getAge)` with an `int` getter, the int is **autoboxed** to `Integer` for every comparison. `comparingInt` takes a `ToIntFunction` and compares the raw ints, avoiding boxing (less garbage, faster) and using `Integer.compare` internally (no subtraction overflow). Prefer the primitive variant whenever the key is a primitive. ### Why prefer it over hand-written compare 1. **Declarative & readable** - `comparing(Person::getAge)` says *what*, not *how*. 2. **Composable** - the result is a `Comparator`, so you can immediately chain `.thenComparing(...)`, `.reversed()`, `Comparator.nullsFirst(...)`. 3. **Correct by construction** - it delegates to the key type's own `compareTo`/given comparator, dodging classic mistakes like `a - b` overflow or returning the wrong sign. 4. **Less code** - one line vs a multi-branch method. ### Performance note The key extractor runs **for each comparison**, i.e. roughly O(n log n) times during a sort, not once per element. If extracting the key is expensive, that cost multiplies. For very hot paths consider a *decorate-sort-undecorate* (Schwartzian transform) - precompute keys once - though for most code `comparing` is fine. ### Putting it together ```java people.sort( Comparator.comparing(Person::getLastName) .thenComparingInt(Person::getAge) .reversed()); ``` Reads as: by last name, tie-break by age, then reverse the whole thing.
- Why use comparingInt over comparing for an int getter?comparing autoboxes each int to Integer on every comparison; comparingInt works on raw ints, avoiding boxing overhead and using Integer.compare so there's no overflow.
- How many times is the key extractor invoked during a sort of n elements?Roughly O(n log n) times - once for each side of each pairwise comparison, not once per element.
saying these in an interview costs you the question
- Using comparing for a primitive key instead of comparingInt (needless boxing)
- Assuming the key extractor runs once per element (it runs per comparison)
- Thinking comparing mutates the list (it returns a Comparator)
- Passing a non-Comparable key to the single-arg comparing (won't compile/throws)