Comparable gives a class its one natural order (compareTo); a Comparator is any number of external orders built fluently with comparing, thenComparing and reversed.
Comparable<T> | Comparator<T> | |
|---|---|---|
| Package | java.lang | java.util |
| Method | int compareTo(T other) | int compare(T a, T b) |
| Where | Inside the class being sorted | Separate object / lambda |
| How many orders | One — the natural order | As many as you like |
| Used by | Collections.sort(list), TreeMap, Arrays.sort with no comparator | list.sort(cmp), new TreeSet<>(cmp), stream.sorted(cmp) |
| Examples in the JDK | String, Integer, LocalDate, enums | String.CASE_INSENSITIVE_ORDER, Comparator.reverseOrder() |
The contract
Return negative if
this < other, zero if equal, positive if greater — only the sign matters.Antisymmetric:
sgn(a.compareTo(b)) == -sgn(b.compareTo(a)); transitive; consistent across calls.Strongly recommended: consistent with
equals(compareTo == 0⇔equals) — sorted sets and maps usecompareTofor equality.
Natural order with Comparable
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Version> vs = new ArrayList<>(List.of(new Version(1, 10), new Version(1, 2), new Version(0, 9)));
Collections.sort(vs);
System.out.println(vs);
System.out.println(Collections.max(vs));
}
}
record Version(int major, int minor) implements Comparable<Version> {
@Override
public int compareTo(Version o) {
int c = Integer.compare(major, o.major);
return c != 0 ? c : Integer.compare(minor, o.minor);
}
@Override
public String toString() { return major + "." + minor; }
}[0.9, 1.2, 1.10]
1.10Building comparators fluently
import java.util.*;
public class Main {
record Emp(String name, String dept, int salary) {}
public static void main(String[] args) {
List<Emp> staff = new ArrayList<>(List.of(
new Emp("Ravi", "HR", 50), new Emp("Asha", "Eng", 90),
new Emp("Meera", "Eng", 70), new Emp("Kiran", "HR", 50)));
staff.sort(Comparator.comparing(Emp::dept)
.thenComparing(Emp::salary, Comparator.reverseOrder())
.thenComparing(Emp::name));
staff.forEach(e -> System.out.println(e.dept() + " " + e.salary() + " " + e.name()));
List<String> words = new ArrayList<>(List.of("pear", "Fig", "apple", "kiwi"));
words.sort(Comparator.comparingInt(String::length).reversed());
System.out.println(words);
words.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println(words);
List<String> withNull = new ArrayList<>(Arrays.asList("b", null, "a"));
withNull.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println(withNull);
}
}Eng 90 Asha
Eng 70 Meera
HR 50 Kiran
HR 50 Ravi
[apple, pear, kiwi, Fig]
[apple, Fig, kiwi, pear]
[null, a, b]| Factory / method | Meaning |
|---|---|
Comparator.comparing(keyFn) | Compare by an extracted Comparable key |
comparingInt/Long/Double(fn) | Same, without boxing |
.thenComparing(...) | Tie-breaker |
.reversed() | Flip the whole chain built so far |
Comparator.naturalOrder() / reverseOrder() | Natural order / its reverse |
nullsFirst(cmp) / nullsLast(cmp) | Treat null as smallest / largest |
Trap: comparing by subtraction
import java.util.*;
public class Main {
public static void main(String[] args) {
Comparator<Integer> bad = (a, b) -> a - b; // overflows for far-apart values
Comparator<Integer> good = Integer::compare;
int big = Integer.MAX_VALUE, small = -1;
System.out.println("a - b says: " + bad.compare(big, small));
System.out.println("compare says: " + good.compare(big, small));
List<Integer> xs = new ArrayList<>(List.of(Integer.MIN_VALUE, 1));
xs.sort(bad); // 1 - MIN_VALUE overflows to negative
System.out.println(xs);
}
}a - b says: -2147483648
compare says: 1
[1, -2147483648]Comparable vs Comparator — when to use which?
What happens if compareTo is inconsistent with equals?
How does reversed() behave in a chain?