Comparable and Comparator
Define natural and custom ordering for objects using the Comparable and Comparator interfaces.
Overview
Key Points
- •Comparable<T>: implement compareTo(T other) inside the class
- •compareTo returns negative if this < other, 0 if equal, positive if this > other
- •Comparator<T>: external object with compare(T a, T b)
- •Comparator.comparing(keyExtractor) creates a Comparator from a key function
- •thenComparing chains secondary sort criteria
- •reversed() reverses a Comparator
- •Comparator.naturalOrder() and Comparator.reverseOrder() for built-in types
- •Collections.sort(list) uses natural order; Collections.sort(list, comparator) uses the given order
Examples
Implementing Comparable
import java.util.*;
public class Main {
static class Student implements Comparable<Student> {
String name;
double gpa;
Student(String name, double gpa) {
this.name = name;
this.gpa = gpa;
}
@Override
public int compareTo(Student other) {
// Natural order: highest GPA first (descending)
return Double.compare(other.gpa, this.gpa);
}
@Override
public String toString() {
return name + "(" + gpa + ")";
}
}
public static void main(String[] args) {
List<Student> students = new ArrayList<>(List.of(
new Student("Alice", 3.8),
new Student("Bob", 3.5),
new Student("Carol", 3.9),
new Student("Dave", 3.6)
));
Collections.sort(students); // uses compareTo
System.out.println(students); // [Carol(3.9), Alice(3.8), Dave(3.6), Bob(3.5)]
Student best = Collections.min(students); // min uses compareTo; highest GPA = "smallest" here
System.out.println("Best: " + best);
}
}Double.compare(a, b) is the safe way to compare doubles. Avoid subtraction (b.gpa - a.gpa) because it can lose precision and overflow for integers.
Comparator.comparing and chaining
import java.util.*;
public class Main {
static class Product {
String name;
String category;
double price;
Product(String name, String category, double price) {
this.name = name; this.category = category; this.price = price;
}
public String toString() { return name + "(" + category + ",$" + price + ")"; }
}
public static void main(String[] args) {
List<Product> products = new ArrayList<>(List.of(
new Product("Apple", "Fruit", 1.20),
new Product("Banana", "Fruit", 0.50),
new Product("Carrot", "Vegetable", 0.80),
new Product("Daikon", "Vegetable", 1.50),
new Product("Eggplant", "Vegetable", 2.00)
));
// Sort by category, then by price ascending
Comparator<Product> byCategoryThenPrice = Comparator
.comparing((Product p) -> p.category)
.thenComparingDouble(p -> p.price);
products.sort(byCategoryThenPrice);
products.forEach(System.out::println);
System.out.println("---");
// Most expensive first
products.sort(Comparator.comparingDouble((Product p) -> p.price).reversed());
products.forEach(System.out::println);
}
}Comparator.comparing creates a Comparator from a key extractor. thenComparing adds a secondary criterion. reversed() flips the order.
Comparator for sorting strings by multiple criteria
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> words = new ArrayList<>(
List.of("banana","kiwi","apple","fig","elderberry","cherry","date")
);
// Sort by length, then alphabetically for equal lengths
words.sort(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()));
System.out.println(words);
// Sort alphabetically reversed
words.sort(Comparator.reverseOrder());
System.out.println(words);
}
}String already implements Comparable (alphabetical). Comparator.naturalOrder() returns a Comparator that uses the natural order of the elements.
Common Mistakes
- Using subtraction to compare ints in compareTo. return a - b overflows for large values like Integer.MIN_VALUE. Use Integer.compare(a, b) instead
- Violating the compareTo contract: if a.compareTo(b) == 0, then a.equals(b) should ideally be true. Inconsistency can cause bugs in sorted sets and maps
- Forgetting that TreeSet and TreeMap use compareTo (or a Comparator) for both ordering and equality. Two objects that compareTo to 0 are treated as duplicates even if equals returns false
Note
Use Comparator.comparing to build readable, composable sort orders without writing verbose compare methods. The chain Comparator.comparing(f).thenComparing(g).reversed() covers most sorting needs.
Exercises
Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.
Completed: 0 / 4 exercises
Knowledge Check
Question 1 of 4