Comparator vs Comparable
Java has two mechanisms for comparing objects. Comparable defines a single natural order inside the class. Comparator defines custom ordering outside the class — you can have as many as you need.
At a Glance
Section titled “At a Glance”| Comparable | Comparator | |
|---|---|---|
| Where | Inside the class | External / standalone |
| Method | compareTo(T other) | compare(T o1, T o2) |
| Package | java.lang | java.util |
| Orders | One (natural) | Many (custom) |
| Modify class? | Yes | No |
| Usage | Collections.sort(list) | Collections.sort(list, comp) |
Comparable — Natural Ordering
Section titled “Comparable — Natural Ordering”Implement Comparable<T> when your class has one obvious default ordering.
public class Person implements Comparable<Person> { private String name; private int age;
@Override public int compareTo(Person other) { return Integer.compare(this.age, other.age); }}The return value contract:
- Negative →
thiscomes beforeother - Zero → equal
- Positive →
thiscomes afterother
Collections.sort(people); // uses compareTopeople.sort(null); // same thing — null means natural orderTreeSet<Person> sorted = new TreeSet<>(people); // also uses compareToComparator — Custom Ordering
Section titled “Comparator — Custom Ordering”Use when you need multiple sort orders or can’t modify the class.
Classic approach (pre-Java 8)
Section titled “Classic approach (pre-Java 8)”public class NameComparator implements Comparator<Person> { @Override public int compare(Person p1, Person p2) { return p1.getName().compareTo(p2.getName()); }}
people.sort(new NameComparator());Lambda approach (Java 8+)
Section titled “Lambda approach (Java 8+)”people.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));Method reference approach (cleanest)
Section titled “Method reference approach (cleanest)”people.sort(Comparator.comparing(Person::getName));people.sort(Comparator.comparing(Person::getAge).reversed());Chaining Comparators
Section titled “Chaining Comparators”Sort by multiple fields with thenComparing:
Comparator<Person> comp = Comparator .comparing(Person::getAge) // primary: age asc .thenComparing(Person::getSalary, Comparator.reverseOrder()) // secondary: salary desc .thenComparing(Person::getName); // tertiary: name asc
people.sort(comp);Null-Safe Comparators
Section titled “Null-Safe Comparators”// Nulls firstpeople.sort(Comparator.comparing(Person::getName, Comparator.nullsFirst(String::compareTo)));
// Nulls lastpeople.sort(Comparator.comparing(Person::getName, Comparator.nullsLast(String::compareTo)));When to Use Which
Section titled “When to Use Which”Use Comparable when:
- The class has one obvious natural order (e.g.,
Stringalphabetically,Integernumerically) - You own the class and can modify it
- You want
TreeSet/TreeMapto work with your objects by default
Use Comparator when:
- You need multiple different sort orders
- You can’t modify the class (third-party code)
- The ordering is context-dependent or temporary
Common Pitfalls
Section titled “Common Pitfalls”1. Inconsistent multi-field comparison
// WRONG — fragile and error-pronereturn (this.age + this.salary) - (other.age + other.salary);
// RIGHT — compare field by fieldint cmp = Integer.compare(this.age, other.age);if (cmp != 0) return cmp;return Double.compare(this.salary, other.salary);
// BEST — use Comparator.comparing with chaining (shown above)2. NullPointerException on fields
// WRONGreturn this.name.compareTo(other.name); // NPE if name is null
// RIGHT — use null-safe comparators or guard manuallyreturn Comparator.comparing(Person::getName, Comparator.nullsFirst(String::compareTo)) .compare(this, other);3. Forgetting consistency with equals
If a.compareTo(b) == 0, then ideally a.equals(b) should also be true. TreeSet and TreeMap rely on compareTo for equality checks, not equals.