⚡IntermediateAdvanced OOP

Comparable and Comparator

Define natural and custom ordering for objects using the Comparable and Comparator interfaces.

Overview

Java provides two ways to order objects. Comparable<T> defines the natural ordering of a class. A class implements Comparable and provides a compareTo method. When an object has a natural order (numbers, strings, dates), implement Comparable. Comparator<T> is an external strategy for ordering. You pass a Comparator to sort methods when you need a different ordering, or when you cannot modify the class. One class can have many Comparators but only one Comparable ordering. Both are used by Collections.sort, List.sort, Arrays.sort, TreeMap, TreeSet, and PriorityQueue.

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.

Loading editor…

Completed: 0 / 4 exercises

Knowledge Check

Question 1 of 4

What does compareTo return to indicate that this object is less than the argument?