Comparable and Comparator in Java
Comparable gives a class one natural order. Comparator supplies any number of orders from outside, without touching the class.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The difference in one line
Comparable is implemented by the class being sorted and defines its single natural order. Comparator is a separate object that defines an order, and a class can be sorted by as many comparators as you like.
Comparable
public record Student(String name, int rollNumber, double marks)
implements Comparable<Student> {
@Override
public int compareTo(Student other) {
return Integer.compare(rollNumber, other.rollNumber);
}
}List<Student> students = new ArrayList<>(List.of(
new Student("Meera", 12, 88.5),
new Student("Arun", 4, 76.0),
new Student("Ravi", 9, 91.25)));
Collections.sort(students); // uses compareTo
students.sort(null); // the same thing
Set<Student> sorted = new TreeSet<>(students); // also uses compareToThe contract of compareTo
| Return | Meaning |
|---|---|
| Negative | This object comes before the other |
| Zero | They have equal ordering |
| Positive | This object comes after |
The ordering must be consistent: reversing the arguments must reverse the sign, and it must be transitive. It should also be consistent with equals, meaning compareTo returns zero exactly when equals returns true. When it is not, a TreeSet and a HashSet will disagree about what a duplicate is.
Comparator
students.sort(Comparator.comparing(Student::name));
students.sort(Comparator.comparingDouble(Student::marks).reversed());
students.sort(Comparator
.comparingDouble(Student::marks).reversed()
.thenComparing(Student::name)); // ties broken by nameThe factory methods read almost as English, and they compose. This is the form to use in modern Java; writing the interface out by hand is rarely necessary.
// The explicit form, for comparison
Comparator<Student> byMarks = new Comparator<>() {
@Override
public int compare(Student a, Student b) {
return Double.compare(a.marks(), b.marks());
}
};The useful factory methods
| Method | Purpose |
|---|---|
comparing(fn) | Order by an extracted key |
comparingInt, comparingLong, comparingDouble | Same, without boxing |
thenComparing(fn) | Break ties with a second key |
reversed() | Invert the whole order |
naturalOrder(), reverseOrder() | Use Comparable |
nullsFirst(cmp), nullsLast(cmp) | Decide where nulls go |
List<String> names = new ArrayList<>(Arrays.asList("Ravi", null, "Anita"));
names.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println(names); // [null, Anita, Ravi]Never subtract to compare
// Broken: the subtraction overflows
Comparator<Integer> broken = (a, b) -> a - b;
System.out.println(broken.compare(Integer.MAX_VALUE, -1)); // negative, wrong
// Correct
Comparator<Integer> correct = Integer::compare;Subtracting is a habit carried over from older code and it is a real bug. Always useInteger.compare,Long.compareorDouble.compare, which are correct for every input includingNaN.
Comparison
| Aspect | Comparable | Comparator |
|---|---|---|
| Package | java.lang | java.util |
| Method | compareTo(T other) | compare(T a, T b) |
| Where the logic lives | Inside the class | Outside the class |
| How many orders | One | Any number |
| Requires changing the class | Yes | No |
| Used by | sort with no argument, TreeSet, TreeMap | sort(cmp), TreeSet(cmp), streams |
Implement Comparable when the type has one obvious order, such as a date or a version number. Use a Comparator for everything else, especially for presentation ordering that may change.
With sorted collections and streams
Set<Student> byMarks = new TreeSet<>(
Comparator.comparingDouble(Student::marks).thenComparing(Student::rollNumber));
Map<String, Integer> sortedByKey = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
List<Student> top = students.stream()
.sorted(Comparator.comparingDouble(Student::marks).reversed())
.limit(3)
.toList();
Optional<Student> best = students.stream()
.max(Comparator.comparingDouble(Student::marks));Note the tie breaker in the TreeSet. Without it, two students with equal marks would be treated as the same element and one would be dropped.
Sorting stability
Collections.sort and List.sort are stable: elements that compare equal keep their relative order. That is what makes sorting twice work, first by the secondary key and then by the primary one, although a single chained comparator is clearer.
Common mistakes
- Subtracting integers inside a comparator.
- Making
compareToinconsistent withequalsand losing elements in aTreeSet. - Forgetting a tie breaker in a sorted collection.
- Sorting an immutable list from
List.of, which throws. - Returning a boolean converted to 1 or 0 instead of a three way result.
- Comparing
doublevalues with subtraction, which mishandlesNaN.
Best practices
- Build comparators with the factory methods rather than by hand.
- Use
Integer.compareand its siblings. - Keep
compareToconsistent withequals. - Always add a tie breaker for sorted sets and maps.
- Extract a named comparator constant when the same order is used in several places.
Practice
- Sort a list of employees by department, then by salary descending, then by name.
- Show a concrete pair of values for which
(a, b) -> a - bgives the wrong answer. - Why does a
TreeSetdrop an element when the comparator returns zero for two unequal objects? - Sort a list containing nulls so that they appear last.
- Explain when you would implement
Comparablerather than pass aComparator.
Conclusion
One natural order belongs inside the class as Comparable; every other order belongs outside as a Comparator. Compose comparators with the factory methods, always compare rather than subtract, and remember the tie breaker in sorted collections.