Comparators

1. What Is a Comparator?

int compare(T o1, T o2);

The result means:

negative → o1 comes before o2
0        → o1 and o2 are considered equal
positive → o1 comes after o2

2. Creating a Comparator with a Lambda

For integers:

Comparator<Integer> comparator =
    (i1, i2) -> Integer.compare(i1, i2);

Or with a method reference:

Comparator<Integer> comparator = Integer::compare;

Avoid:

(i1, i2) -> i1 - i2

because integer subtraction can overflow and produce an incorrect comparison.


3. Comparator.comparing()

comparing() creates a comparator based on a property/key of an object.

For example, sort strings by length:

Comparator<String> byLength =
    Comparator.comparinglength;

For a custom class:

Comparator<User> byName =
    Comparator.comparinggetName;

Then:

users.sort(byName);

Think of it as:

User → getName() → compare names

4. Chaining Comparators

You can sort by multiple properties using thenComparing().

For example:

Comparator<User> comparator =
    Comparator.comparinggetFirstName
              .thenComparinggetLastName;

The sorting works like:

First name
    ↓
If equal
    ↓
Last name

For example:

John Smith
John Williams
Mike Brown
Mike Smith

First names are compared first. If two users have the same first name, their last names are compared.


5. Primitive Comparators

When comparing numeric properties, use specialized methods to avoid unnecessary boxing/unboxing.

Comparator<User> byAge =
    Comparator.comparingIntgetAge;

Other versions:

Comparator.comparingLong(...)
Comparator.comparingDouble(...)

You can also chain them:

Comparator<User> comparator =
    Comparator.comparingIntgetAge
              .thenComparinggetName;

Common methods

Method Used for
comparing() Object/reference values
comparingInt() int
comparingLong() long
comparingDouble() double
thenComparing() Add another comparison
thenComparingInt() Add an int comparison
thenComparingLong() Add a long comparison
thenComparingDouble() Add a double comparison

6. Natural Order

Some Java classes already know how to compare themselves because they implement Comparable.

For example:

String
Integer
LocalDate

These classes provide:

compareTo()

You can use their natural ordering with:

Comparator.naturalOrder();

Example:

Comparator<String> comparator =
    Comparator.naturalOrder();

For multi-level sorting:

Comparator<String> comparator =
    Comparator.comparinglength
              .thenComparing(Comparator.naturalOrder());

This means:

First → length
Then  → alphabetical order

7. Reversing a Comparator

Use:

reversed()

to reverse the sorting order.

Comparator<Integer> ascending =
    Comparator.naturalOrder();

Comparator<Integer> descending =
    ascending.reversed();

So:

ascending:  1 2 3 4 5
descending: 5 4 3 2 1

You can also reverse a complete chained comparator:

Comparator<String> comparator =
    Comparator.comparinglength
              .thenComparing(Comparator.naturalOrder())
              .reversed();

8. Handling null

Normal comparators may throw a NullPointerException when they encounter null.

Use:

Comparator.nullsFirst(...)

or:

Comparator.nullsLast(...)

Example:

Comparator<String> comparator =
    Comparator.nullsLast(Comparator.naturalOrder());

Result:

"apple"
"banana"
"orange"
null
null

With nullsFirst():

null
null
"apple"
"banana"
"orange"

9. Complete Example

Suppose we have:

class User {
    String name;
    int age;

    String getName() {
        return name;
    }

    int getAge() {
        return age;
    }
}

We want to sort users:

  1. By age

  2. If ages are equal, by name

We can write:

Comparator<User> comparator =
    Comparator.comparingIntgetAge
              .thenComparinggetName;

Then:

users.sort(comparator);

The logic is:

User
 ↓
getAge()
 ↓
compare age
 ↓
if same age
 ↓
getName()
 ↓
compare name

Quick Revision

Technique Example Purpose
Lambda (a, b) -> Integer.compare(a, b) Custom comparison
Method reference Integer::compare Concise comparison
comparing() Comparator.comparinggetName Compare by property
comparingInt() Comparator.comparingIntgetAge Compare int property
thenComparing() .thenComparinggetName Secondary sorting
naturalOrder() Comparator.naturalOrder() Use Comparable's natural order
reversed() comparator.reversed() Reverse sorting
nullsFirst() Comparator.nullsFirst(...) Put null first
nullsLast() Comparator.nullsLast(...) Put null last

In short

Comparator
    ↓
Defines how two objects are ordered

comparing()
    ↓
Compare by a property

thenComparing()
    ↓
Add another sorting rule

reversed()
    ↓
Reverse the order

naturalOrder()
    ↓
Use the object's compareTo()

nullsFirst()/nullsLast()
    ↓
Safely handle null values

The most useful pattern to remember is:

Comparator.comparinggetProperty
          .thenComparinggetAnotherProperty;

This is one of the most common ways to write clean sorting logic in modern Java.