JavaComparable & Comparator

Comparable & Comparator

Sorting objects requires Java to know how to decide which of two objects comes first. There are two interfaces for expressing that: Comparable<T>, implemented by the class itself to define its one "natural" ordering, and Comparator<T>, a separate object that defines an ordering from the outside — useful when you need multiple orderings, or when you can't modify the class at all.
Comparable — Natural Ordering
A class implements Comparable<T> by providing a single method, compareTo(T other), which returns a negative number if the current object comes before other, zero if they're considered equal for ordering purposes, and a positive number if it comes after. Classes like String and the boxed numeric types already implement Comparable, which is why Collections.sort() and Arrays.sort() work on them without any extra setup.

Implementing Comparable for natural ordering

Java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Employee implements Comparable<Employee> {
    String name;
    int age;

    Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Employee other) {
        return Integer.compare(this.age, other.age); // natural order: by age
    }

    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

List<Employee> team = new ArrayList<>(List.of(
        new Employee("Diego", 34),
        new Employee("Amara", 28),
        new Employee("Zoe", 41)
));

Collections.sort(team);
System.out.println(team);
[Amara(28), Diego(34), Zoe(41)]
Comparator — External, Alternative Orderings
Comparator<T> lives entirely outside the class it orders. It defines one method, compare(T a, T b), with the same negative/zero/positive convention as compareTo(). This makes Comparator ideal when you need to sort a class you don't own, or when you need more than one ordering for the same class (sort employees by age and by name, for instance).

A Comparator sorting the same class by name instead

Java
import java.util.Comparator;
import java.util.List;

List<Employee> team = new ArrayList<>(List.of(
        new Employee("Diego", 34),
        new Employee("Amara", 28),
        new Employee("Zoe", 41)
));

Comparator<Employee> byName = new Comparator<Employee>() {
    @Override
    public int compare(Employee a, Employee b) {
        return a.name.compareTo(b.name);
    }
};

team.sort(byName);
System.out.println(team);
[Amara(28), Diego(34), Zoe(41)]
Modern Comparators with Lambdas
Because Comparator has a single abstract method, it's a functional interface — meaning a lambda expression can replace the anonymous class entirely. The static helper Comparator.comparing() goes a step further, taking a "key extractor" function and building the whole Comparator for you.

Comparator.comparing() and lambdas

Java
import java.util.Comparator;
import java.util.List;

List<Employee> team = new ArrayList<>(List.of(
        new Employee("Diego", 34),
        new Employee("Amara", 28),
        new Employee("Zoe", 41)
));

// Equivalent to the anonymous class above, but far shorter
team.sort(Comparator.comparing(e -> e.name));
System.out.println(team);

// Sort by age descending
team.sort(Comparator.comparing((Employee e) -> e.age).reversed());
System.out.println(team);
[Amara(28), Diego(34), Zoe(41)]
[Zoe(41), Diego(34), Amara(28)]
Chaining with thenComparing()
Real sorting often needs a tiebreaker: sort by one field, and when two elements are equal on that field, fall back to another. thenComparing() chains additional Comparators onto the first one, applied only when earlier comparisons return zero.

Chaining comparators: age first, then name as a tiebreaker

Java
import java.util.Comparator;
import java.util.List;

List<Employee> team = new ArrayList<>(List.of(
        new Employee("Zoe", 34),
        new Employee("Amara", 34),
        new Employee("Diego", 28)
));

team.sort(
    Comparator.comparing((Employee e) -> e.age)
              .thenComparing(e -> e.name)
);

System.out.println(team);
[Diego(28), Amara(34), Zoe(34)]
Note
Both Amara and Zoe are 34, so the primary comparator treats them as equal — thenComparing() then breaks the tie alphabetically by name.

Aspect

Comparable<T>

Comparator<T>

Defined

Inside the class being sorted

As a separate, external object

Method

compareTo(T other)

compare(T a, T b)

Number of orderings

One — the "natural" order

As many as you like

Use when

You own the class and there's one obvious order

You need alternate orders, or can't modify the class

Modern helpers

n/a

Comparator.comparing(), .reversed(), .thenComparing()

Tip
Default to implementing Comparable for the one obvious natural ordering of your own classes (like a Money class sorting by amount), and reach for Comparator — usually built with Comparator.comparing() — for every other ordering you need on top of that.
  • Comparable defines one natural ordering inside the class via compareTo()

  • Comparator defines an external ordering via compare(), and you can have several

  • Comparator.comparing() builds a Comparator from a key-extractor lambda

  • .reversed() flips an ordering; .thenComparing() adds a tiebreaker