Comparable and Comparator — Natural Ordering, Comparator.comparing, and thenComparing
In this tutorial, you will learn about Comparable and Comparator. We cover key concepts, practical examples, and best practices to help you master this topic.
Java provides Comparable for natural ordering and Comparator for custom ordering, both enabling sorting and ordered collections. Comparable defines a type's natural order (e.g., String is alphabetically ordered, Integer is numerically ordered), while Comparator provides flexible, external ordering strategies.
What You'll Learn
- Implementing
Comparablefor natural ordering - Using
Comparatorfor custom and multiple sort orders Comparator.comparing(),thenComparing(), and null handling- Sorting with
Collections.sort()andList.sort()
Why It Matters
Sorting is fundamental to data processing. Ordered collections (TreeSet, TreeMap) and stream sorting depend on Comparable or Comparator. Understanding them lets you sort any data type in any order without writing sort algorithms.
Real-World Use
Tables display sorted data (by name, date, amount). Leaderboards rank players by score. Price comparison sites sort by price, rating, or popularity. All use Comparator.
Comparable Interface
public interface Comparable<T> {
int compareTo(T o);
}
Returns:
- Negative:
this < o - Zero:
this == o - Positive:
this > o
Implementing Comparable
public class Person implements Comparable<Person> {
private String name;
private int age;
// natural ordering by name, then age
@Override
public int compareTo(Person other) {
int nameCmp = this.name.compareTo(other.name);
if (nameCmp != 0) return nameCmp;
return Integer.compare(this.age, other.age);
}
}
Usage:
List<Person> people = getPeople();
Collections.sort(people); // uses natural ordering
// or
people.sort(null); // null means natural ordering
Consistency with equals()
The natural ordering should be consistent with equals(): a.compareTo(b) == 0 should imply a.equals(b). If not, document it explicitly.
Comparator Interface
public interface Comparator<T> {
int compare(T o1, T o2);
}
Anonymous Comparator
people.sort(new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return Integer.compare(a.getAge(), b.getAge());
}
});
Lambda Comparator
people.sort((a, b) -> Integer.compare(a.getAge(), b.getAge()));
Comparator Factory Methods (Java 8+)
Comparator.comparing()
// Sort by name
people.sort(Comparator.comparing(Person::getName));
// Sort by age
people.sort(Comparator.comparingInt(Person::getAge));
// Sort by name length
people.sort(Comparator.comparing(p -> p.getName().length()));
Reversing
people.sort(Comparator.comparing(Person::getName).reversed());
thenComparing() — Chaining
// Sort by name, then by age for same names
Comparator<Person> byNameThenAge = Comparator
.comparing(Person::getName)
.thenComparingInt(Person::getAge);
people.sort(byNameThenAge);
Null Handling
// Null names first
people.sort(Comparator.nullsFirst(Comparator.comparing(Person::getName)));
// Null names last
people.sort(Comparator.nullsLast(Comparator.comparing(Person::getName)));
Sorting Collections
List.sort()
list.sort(Comparator.naturalOrder());
list.sort(Comparator.reverseOrder());
list.sort(Comparator.comparing(Fruit::getName));
Collections.sort()
Collections.sort(list);
Collections.sort(list, Comparator.comparing(Fruit::getName));
Stream Sorting
List<String> sorted = stream
.sorted(Comparator.comparing(String::length))
.toList();
Primitive Comparators
Comparator.comparingInt(Person::getAge);
Comparator.comparingLong(Entity::getId);
Comparator.comparingDouble(Product::getPrice);
These avoid autoboxing overhead.
Comparing Multiple Fields
// Sort employees by department, then by salary (desc), then by name
Comparator<Employee> complex = Comparator
.comparing(Employee::getDepartment)
.thenComparing(Employee::getSalary, Comparator.reverseOrder())
.thenComparing(Employee::getName);
employees.sort(complex);
Common Mistakes
- Forgetting to handle null.
Comparator.comparing(Person::getName)throwsNullPointerExceptionif any name is null. UsenullsFirst()ornullsLast(). - Using
a - bfor comparison. Integer overflow can produce wrong results:Integer.MIN_VALUE - 1wraps toInteger.MAX_VALUE. UseInteger.compare(a, b). - Inconsistent with
equals(). Ifa.compareTo(b) == 0but!a.equals(b),TreeSetandTreeMapmay behave unexpectedly. - Assuming
compareTo()returns only -1, 0, 1. It can return any negative, zero, or positive int. - Modifying elements in a sorted set. If a
TreeSetelement's comparison fields change, the tree structure becomes corrupted.
Practice Questions
1. What is the difference between Comparable and Comparator?
Comparable defines natural ordering inside the class (modifies the class). Comparator is an external Strategy (does not modify the class). A class can have one Comparable but many Comparators.
2. What does Comparator.comparing() return?
A Comparator that compares by the extracted key. It takes a Function that maps an object to a Comparable key.
3. How do you reverse a comparator?
Call .reversed() on the comparator: Comparator.comparing(Person::getName).reversed().
4. What is thenComparing() used for?
Chaining: when two elements are equal by the first comparator, thenComparing() applies a secondary comparator for tie-breaking.
5. Why should you avoid (a, b) -> a.age - b.age?
Integer overflow. If a = Integer.MIN_VALUE and b = 1, the result is Integer.MAX_VALUE - 1 (positive), but MIN_VALUE < 1 should be negative. Use Integer.compare(a.age, b.age).
Challenge Question:
Write a program that reads employee records (name, department, salary), stores them in a list, and sorts them:
- By department (ascending), then by salary (descending), then by name (ascending)
- With null-safe comparators (some names may be null)
- Using only
Comparator.comparing()andthenComparing()Then demonstrate thatTreeSetwith the same comparator correctly handles duplicates.
FAQ
{{< faq "What is the natural ordering of String?" "Lexicographic (dictionary order), based on Unicode. \"apple\" < \"banana\". Uppercase letters come before lowercase: \"Apple\" < \"apple\"." >}}
Mini Project
Write a program SortingDemo.java that:
- Creates a list of
Employeeobjects with fieldsname,department,salary,hireDate - Implements
Comparable<Employee>with natural ordering by hire date (oldest first) - Creates multiple
Comparatorinstances: by name, by salary (desc), by department then salary then name - Sorts the list with each comparator and prints results
- Uses a
TreeSetwith a custom comparator and shows how duplicates are determined - Demonstrates
nullsFirst()andnullsLast()with a list containing null names - Uses stream
.sorted()with method references
What's Next
Sorting involves time, but Java also has a comprehensive Date/Time API. Lesson 30 covers date and time — LocalDate, LocalTime, ZonedDateTime, Duration, Period, and formatting with DateTimeFormatter.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro