Comparable
When you need your custom objects to be sortable — whether by Collections.sort(), Arrays.sort(), or a sorted collection like TreeSet — Java gives you the Comparable interface. Implement it once in your class, and every sorting utility in the standard library knows how to order your objects.
What is Comparable?
Comparable<T> is a generic interface in java.lang (so no import needed). It declares exactly one method:
public int compareTo(T other);
When your class implements Comparable, you are defining its natural ordering — the “default” way instances of that class should be ranked. String, Integer, Double, LocalDate, and most other built-in types already implement it.
The compareTo Contract
Your compareTo method must return:
| Return value | Meaning |
|---|---|
| negative integer | this comes before other |
| zero | this and other are equal in order |
| positive integer | this comes after other |
Note: The exact magnitude doesn’t matter — only the sign. Returning
-1,0,1is perfectly fine, and so is returning the raw difference of two integers (with a caveat explained below).
The contract also requires consistency:
- Antisymmetry: if
a.compareTo(b) > 0thenb.compareTo(a) < 0. - Transitivity: if
a.compareTo(b) > 0andb.compareTo(c) > 0, thena.compareTo(c) > 0. - Consistency with equals (strongly recommended):
a.compareTo(b) == 0should meana.equals(b)istrue. Violating this causes surprises insideTreeSetandTreeMap.
Your First Comparable Class
Suppose you are building an app that tracks students by GPA. You want to sort them from lowest to highest GPA.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Student implements Comparable<Student> {
private String name;
private double gpa;
public Student(String name, double gpa) {
this.name = name;
this.gpa = gpa;
}
@Override
public int compareTo(Student other) {
// Double.compare handles NaN and avoids subtraction pitfalls
return Double.compare(this.gpa, other.gpa);
}
@Override
public String toString() {
return name + " (" + gpa + ")";
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 3.8));
students.add(new Student("Bob", 2.9));
students.add(new Student("Carol", 3.5));
Collections.sort(students);
System.out.println(students);
}
}
Output:
[Bob (2.9), Carol (3.5), Alice (3.8)]
Collections.sort() calls compareTo internally, producing an ascending sort with no extra configuration.
Sorting Strings and Numbers (Built-in Comparable)
You rarely need to think about Comparable for String or the numeric wrapper types — they already have a natural order:
import java.util.Arrays;
public class BuiltInSort {
public static void main(String[] args) {
String[] words = {"banana", "apple", "cherry"};
Arrays.sort(words); // uses String.compareTo
System.out.println(Arrays.toString(words));
Integer[] nums = {5, 2, 8, 1};
Arrays.sort(nums); // uses Integer.compareTo
System.out.println(Arrays.toString(nums));
}
}
Output:
[apple, banana, cherry]
[1, 2, 5, 8]
Multi-Field Comparison
Real objects often need sorting on more than one field. Sort by last name, then by first name as a tiebreaker:
public class Person implements Comparable<Person> {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public int compareTo(Person other) {
int cmp = this.lastName.compareTo(other.lastName);
if (cmp != 0) return cmp; // primary key
return this.firstName.compareTo(other.firstName); // tiebreaker
}
@Override
public String toString() {
return lastName + ", " + firstName;
}
}
Tip: Java 8+ offers
Comparator.comparing(...).thenComparing(...)for building multi-field sorts without implementingComparable. UseComparablefor the natural order andComparatorfor ad-hoc or alternative orderings. See Comparator for details.
Comparable with TreeSet
A TreeSet keeps elements in sorted order automatically — but only if they implement Comparable (or you supply a Comparator).
import java.util.TreeSet;
public class SortedStudents {
public static void main(String[] args) {
TreeSet<Student> set = new TreeSet<>();
set.add(new Student("Alice", 3.8));
set.add(new Student("Bob", 2.9));
set.add(new Student("Carol", 3.5));
for (Student s : set) {
System.out.println(s);
}
}
}
Output:
Bob (2.9)
Carol (3.5)
Alice (3.8)
Warning: If two objects return
0fromcompareTo, aTreeSettreats them as duplicates and only keeps one — even ifequals()says they are different. Always keepcompareToconsistent withequals.
Reversing the Natural Order
Need descending order? Call Collections.reverseOrder() — it wraps any Comparable in a reversed Comparator:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ReverseSort {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9));
Collections.sort(nums, Collections.reverseOrder());
System.out.println(nums);
}
}
Output:
[9, 5, 4, 3, 1, 1]
Under the Hood
How Collections.sort Uses Comparable
Collections.sort() delegates to Arrays.sort() on the backing array. For object arrays, Java uses TimSort — a hybrid of merge sort and insertion sort that runs in O(n log n) worst-case and is O(n) on already-sorted data. Each comparison step invokes compareTo through the Comparable interface, which the JIT compiler can usually inline after the call site warms up.
The Integer Subtraction Trap
A common shortcut you might see is returning this.value - other.value instead of Integer.compare(this.value, other.value). This looks fine but overflows for large negative numbers:
// DANGEROUS — can overflow when values are far apart
return this.value - other.value;
// SAFE — use the static compare helper
return Integer.compare(this.value, other.value);
Always use Integer.compare, Double.compare, Long.compare, etc. They are branch-free and overflow-safe.
Raw vs Generic Comparable
Before Java 5, Comparable was a raw type and compareTo accepted Object. Generics changed the signature to compareTo(T other), giving you compile-time type safety. The compiler inserts a checkcast bytecode instruction at the call site, so mismatched types throw ClassCastException at runtime if you use the raw type — another reason to always use the generic form.
Impact on Sorted Structures
TreeMap and TreeSet are backed by a red-black tree. They call compareTo on every insertion and lookup, so O(log n) performance depends entirely on a fast and correct compareTo implementation. Keep compareTo consistent, side-effect-free, and inexpensive.
Quick Reference
| Scenario | What to use |
|---|---|
| One fixed natural order | Implement Comparable<T> |
| Multiple orderings / external sort | Use Comparator |
| Sorted collection, no extra config | TreeSet / TreeMap with Comparable |
| Reverse the natural order | Collections.reverseOrder() |
| Compare primitives safely | Integer.compare(), Double.compare(), … |
Related Topics
- Comparator — define alternative or ad-hoc orderings without changing the class
- Comparable vs Comparator — side-by-side comparison of when to use each
- Collections Utility Class —
sort,min,max,reverseOrder, and more - TreeSet — sorted set that relies on natural ordering or a
Comparator - Sorting Collections — complete guide to sorting lists, arrays, and maps in Java
- Generics — understand the
<T>inComparable<T>and why it matters