Comparable vs Comparator in Java
8 min readUpdated
To sort objects, Java has to know how to compare two of them. Comparable builds one natural order into the class itself; Comparator describes any number of other orders from the outside. Here is how each works and when to use which.
The short answer
- Comparable: the class implements
compareTo()and has one natural order (students by roll number, dates by time). - Comparator: a separate object with
compare(), so you can sort the same objects in many ways (by name, by marks, descending). - In modern Java you rarely write a
Comparatorclass:Comparator.comparing(Student::marks)builds one for you.
| Comparable | Comparator | |
|---|---|---|
| Package | java.lang | java.util |
| Method | int compareTo(T other) | int compare(T a, T b) |
| Where it lives | Inside the class being sorted | Outside, as a separate object or lambda |
| How many orders | One natural order | As many as you need |
| Changes the class? | Yes | No (works for classes you cannot edit) |
| Used by | Collections.sort(list), TreeSet, TreeMap | list.sort(cmp), new TreeSet<>(cmp), stream.sorted(cmp) |
What the result means
Both methods return an int: negative if the first object comes first, zero if they are equal in this order, positive if the first comes after. Use Integer.compare(a, b) rather than a - b, which can overflow for large values.
Comparable: the natural order
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NaturalOrder {
static class Student implements Comparable<Student> {
final int roll;
final String name;
final int marks;
Student(int roll, String name, int marks) {
this.roll = roll;
this.name = name;
this.marks = marks;
}
@Override
public int compareTo(Student other) {
return Integer.compare(this.roll, other.roll); // natural order: by roll number
}
@Override
public String toString() { return roll + ":" + name + ":" + marks; }
}
public static void main(String[] args) {
List<Student> list = new ArrayList<>(List.of(
new Student(3, "Ravi", 72), new Student(1, "Meera", 95), new Student(2, "Asha", 88)));
Collections.sort(list); // uses compareTo
System.out.println(list);
}
}Output
[1:Meera:95, 2:Asha:88, 3:Ravi:72]
Comparator: any order you need
Comparator.comparing takes a key extractor; reversed(), thenComparing() and nullsLast() combine orders without writing any comparison logic yourself.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class CustomOrders {
record Student(int roll, String name, String branch, int marks) {}
public static void main(String[] args) {
List<Student> list = new ArrayList<>(List.of(
new Student(3, "Ravi", "ECE", 72),
new Student(1, "Meera", "CSE", 95),
new Student(2, "Asha", "CSE", 88),
new Student(4, "John", "ECE", 88)));
list.sort(Comparator.comparing(Student::name));
System.out.println(list.stream().map(Student::name).toList());
list.sort(Comparator.comparingInt(Student::marks).reversed());
System.out.println(list.stream().map(s -> s.name() + "=" + s.marks()).toList());
list.sort(Comparator.comparing(Student::branch)
.thenComparing(Student::marks, Comparator.reverseOrder()));
System.out.println(list.stream().map(s -> s.branch() + ":" + s.name()).toList());
}
}Output
[Asha, John, Meera, Ravi] [Meera=95, Asha=88, John=88, Ravi=72] [CSE:Meera, CSE:Asha, ECE:John, ECE:Ravi]
Sorted collections and consistency with equals
TreeSet and TreeMap use the comparison, not equals(), to decide whether two elements are the same. If two different students compare as 0, a TreeSet keeps only one of them. Make the order break ties (for example by adding thenComparing(Student::roll)) when that matters.
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetTies {
record Student(int roll, String name, int marks) {}
public static void main(String[] args) {
TreeSet<Student> byMarks = new TreeSet<>(Comparator.comparingInt(Student::marks));
byMarks.add(new Student(1, "Asha", 88));
byMarks.add(new Student(2, "John", 88)); // compares as 0: treated as a duplicate
System.out.println(byMarks.size());
TreeSet<Student> safe = new TreeSet<>(Comparator.comparingInt(Student::marks)
.thenComparingInt(Student::roll));
safe.add(new Student(1, "Asha", 88));
safe.add(new Student(2, "John", 88));
System.out.println(safe.size());
}
}Output
1 2
Common mistakes
- Writing
return this.marks - other.marks;. It overflows for large or negative values; useInteger.compare. - Forgetting that
TreeSetdrops elements that compare as 0. - Implementing
Comparablefor an order that is not really natural, such as "by marks". Use aComparatorfor that. - Sorting with a comparator that does not handle
null. Wrap it withComparator.nullsLast(...).
Frequently asked questions
When should I use Comparable and when Comparator?
Use Comparable for the one obvious order of a class you own. Use Comparator for every other order, and for classes you cannot change.
Can a class have both?
Yes. String has a natural alphabetical order and you can still sort strings by length with Comparator.comparingInt(String::length).
What does compareTo return?
A negative number, zero or a positive number when this object comes before, ties with, or comes after the other one.
Learn it step by step
Practise this, step by step
Short lessons with quizzes, practice and interview questions, from your first program to job-ready.