Skip to content

Comparable and Comparator — Natural Ordering, Comparator.comparing, and thenComparing

DodaTech Updated 2026-06-28 5 min read

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 Comparable for natural ordering
  • Using Comparator for custom and multiple sort orders
  • Comparator.comparing(), thenComparing(), and null handling
  • Sorting with Collections.sort() and List.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

  1. Forgetting to handle null. Comparator.comparing(Person::getName) throws NullPointerException if any name is null. Use nullsFirst() or nullsLast().
  2. Using a - b for comparison. Integer overflow can produce wrong results: Integer.MIN_VALUE - 1 wraps to Integer.MAX_VALUE. Use Integer.compare(a, b).
  3. Inconsistent with equals(). If a.compareTo(b) == 0 but !a.equals(b), TreeSet and TreeMap may behave unexpectedly.
  4. Assuming compareTo() returns only -1, 0, 1. It can return any negative, zero, or positive int.
  5. Modifying elements in a sorted set. If a TreeSet element'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:

  1. By department (ascending), then by salary (descending), then by name (ascending)
  2. With null-safe comparators (some names may be null)
  3. Using only Comparator.comparing() and thenComparing() Then demonstrate that TreeSet with 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\"." >}}

What happens if a Comparable class does not override compareTo()?

The class does not compile as a Comparable. The interface must be implemented. If you want to use the class with sorted collections without implementing Comparable, provide a Comparator.

How do I compare by a property that might be null?

Use Comparator.nullsFirst(Comparator.comparing(Class::getProperty)) to handle null properties, or use Comparator.comparing(Class::getProperty, Comparator.nullsFirst(Comparator.naturalOrder())).

What is the difference between `List.sort()` and `Collections.sort()`?

List.sort() is an instance method on List (Java 8+). Collections.sort() is a static utility method. Both use the same implementation under the hood (modified mergesort in Java's Arrays.sort()).

Can a Comparator be reused?

Yes. Store it as a constant: public static final Comparator<Person> BY_NAME = Comparator.comparing(Person::getName);. Reuse it across multiple sort calls.

Mini Project

Write a program SortingDemo.java that:

  1. Creates a list of Employee objects with fields name, department, salary, hireDate
  2. Implements Comparable<Employee> with natural ordering by hire date (oldest first)
  3. Creates multiple Comparator instances: by name, by salary (desc), by department then salary then name
  4. Sorts the list with each comparator and prints results
  5. Uses a TreeSet with a custom comparator and shows how duplicates are determined
  6. Demonstrates nullsFirst() and nullsLast() with a list containing null names
  7. 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