Comparators
1. What Is a Comparator?
-
Comparator<T>is a functional interface used to define how two objects should be compared. -
Its main abstract method is:
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:
-
By age
-
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.