Home/Learn/Java A–Z/Comparable & Comparator

Comparable & Comparator

Intermediate
Streams & Functional Java

Define natural ordering with Comparable and flexible multi-field sorting with Comparator — the two pillars of Java sorting.

Overview

Comparable defines a class's natural ordering via compareTo(). It is implemented by the class itself and used by Collections.sort(), TreeSet, and TreeMap by default. Comparator is a separate strategy object that defines an external ordering without modifying the class. Java 8 gave Comparator a rich static/default method API for chaining, null handling, and reversing — making complex multi-field sorts readable one-liners.

Implementing Comparable

compareTo returns negative if this < other, zero if equal, positive if this > other. The contract: it must be consistent with equals (compareTo == 0 implies equals() == true), transitive, and antisymmetric.

Never compute a - b as a compareTo shortcut for integers — it overflows. Use Integer.compare(a, b) instead.

ComparableDemo.java
import java.util.*;

public class ComparableDemo implements Comparable<ComparableDemo> {
    private final String name;
    private final int    priority;

    public ComparableDemo(String name, int priority) {
        this.name = name; this.priority = priority;
    }

    @Override
    public int compareTo(ComparableDemo other) {
        // Primary: priority ascending
        int cmp = Integer.compare(this.priority, other.priority);
        if (cmp != 0) return cmp;
        // Secondary: name alphabetically
        return this.name.compareTo(other.name);
    }

    @Override public String toString() { return name + "(" + priority + ")"; }

    public static void main(String[] args) {
        List<ComparableDemo> tasks = new ArrayList<>(List.of(
            new ComparableDemo("Deploy",  2),
            new ComparableDemo("Test",    1),
            new ComparableDemo("Build",   1),
            new ComparableDemo("Review",  2)
        ));
        Collections.sort(tasks); // uses compareTo
        System.out.println(tasks); // [Build(1), Test(1), Deploy(2), Review(2)]

        TreeSet<ComparableDemo> set = new TreeSet<>(tasks);
        System.out.println(set.first()); // Build(1)
    }
}

Comparator API — Chaining & Composition

Comparator.comparing(keyExtractor) — creates a Comparator from a key function thenComparing(keyExtractor) — secondary sort reversed() — flip order nullsFirst / nullsLast — handle nulls Comparator.naturalOrder() / reverseOrder() — standard orders

ComparatorChaining.java
import java.util.*;
import java.util.stream.*;

public class ComparatorChaining {
    record Employee(String name, String dept, double salary) {}

    public static void main(String[] args) {
        List<Employee> emps = List.of(
            new Employee("Alice", "Engineering", 95000),
            new Employee("Bob",   "Marketing",   72000),
            new Employee("Carol", "Engineering", 88000),
            new Employee("Dave",  "Marketing",   72000)
        );

        // Sort by dept asc, then salary desc, then name asc
        Comparator<Employee> comp = Comparator
            .comparing(Employee::dept)
            .thenComparingDouble(Employee::salary).reversed()
            // reversed() flips the whole chain — careful!
            ;

        // Better: build each step explicitly
        Comparator<Employee> precise = Comparator
            .comparing(Employee::dept)
            .thenComparing(Comparator.comparingDouble(Employee::salary).reversed())
            .thenComparing(Employee::name);

        emps.stream()
            .sorted(precise)
            .forEach(e -> System.out.println(e.dept() + " | " + e.name() + " | " + e.salary()));
        // Engineering | Alice | 95000.0
        // Engineering | Carol | 88000.0
        // Marketing   | Bob   | 72000.0
        // Marketing   | Dave  | 72000.0

        // Null handling
        List<String> withNulls = new ArrayList<>(Arrays.asList("banana", null, "apple", null));
        withNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
        System.out.println(withNulls); // [apple, banana, null, null]

        // Reverse natural order
        List<Integer> nums = new ArrayList<>(List.of(3,1,4,1,5,9));
        nums.sort(Comparator.reverseOrder());
        System.out.println(nums); // [9, 5, 4, 3, 1, 1]
    }
}

Comparable vs Comparator — Decision Guide

Use Comparable when the class has one obvious natural order (Integer, String, LocalDate all do this). Use Comparator when: you need multiple orderings, you cannot modify the class, or the ordering is context-dependent (sort products by price for one page, by rating for another).

ComparableVsComparator.java
import java.util.*;

public class ComparableVsComparator {
    record Product(String name, double price, double rating) {}

    public static void main(String[] args) {
        List<Product> products = List.of(
            new Product("Laptop", 999.0, 4.5),
            new Product("Phone",  699.0, 4.7),
            new Product("Tablet", 499.0, 4.2)
        );

        // Different orderings as named Comparators
        Comparator<Product> byPrice  = Comparator.comparingDouble(Product::price);
        Comparator<Product> byRating = Comparator.comparingDouble(Product::rating).reversed();
        Comparator<Product> byName   = Comparator.comparing(Product::name);

        System.out.println("By price:");
        products.stream().sorted(byPrice)
            .forEach(p -> System.out.println("  " + p.name() + " $" + p.price()));

        System.out.println("By rating desc:");
        products.stream().sorted(byRating)
            .forEach(p -> System.out.println("  " + p.name() + " ★" + p.rating()));

        // Using max/min with Comparator
        Product cheapest = products.stream().min(byPrice).orElseThrow();
        Product topRated = products.stream().max(Comparator.comparingDouble(Product::rating)).orElseThrow();
        System.out.println("Cheapest: " + cheapest.name());
        System.out.println("Top rated: " + topRated.name());
    }
}

Key Points to Remember

  • compareTo: negative if this < other, 0 if equal, positive if this > other
  • Never use a - b in compareTo — integer overflow; use Integer.compare(a, b)
  • compareTo must be consistent with equals; inconsistency breaks TreeSet/TreeMap
  • Comparator.comparing(keyFn).thenComparing(...) builds multi-level sorts cleanly
  • reversed() flips the entire chain — attach it to individual steps to flip only one level
  • nullsFirst / nullsLast wrap any Comparator to handle null keys without NPE

Practice Comparable & Comparator in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is the difference between Comparable and Comparator?

EasyAmazon
2

Why should you not use subtraction in compareTo for integers?

MediumGoogle
3

How do you sort a list by multiple fields using Comparator?

MediumMicrosoft
4

What happens if compareTo is not consistent with equals in a TreeSet?

HardOracle
5

How do you sort a list in reverse natural order using Comparator?

EasyTCS

Ask Aria about Comparable & Comparator

Your personal AI tutor — ask anything about this concept