Skip to content
intermediatePhase 13 · Java Collections

Comparable & Comparator

Define natural ordering with Comparable and custom ordering with Comparator.

45m
3 problems
Topic Progress0%

Comparable

Comparable

Comparable<T> defines the natural ordering of a class. It has a single method compareTo(T o) that returns a negative integer, zero, or positive integer to indicate ordering.

Contract:

  • x.compareTo(y) returns negative if x < y
  • Returns 0 if x == y
  • Returns positive if x > y
  • Must be consistent with equals()
  • Transitive: if x < y and y < z, then x < z
public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private double gpa;

    public Student(String name, int age, double gpa) {
        this.name = name;
        this.age = age;
        this.gpa = gpa;
    }

    @Override
    public int compareTo(Student other) {
        int nameCompare = this.name.compareTo(other.name);
        if (nameCompare != 0) return nameCompare;
        return Integer.compare(this.age, other.age);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student s = (Student) obj;
        return age == s.age && Double.compare(gpa, s.gpa) == 0 && name.equals(s.name);
    }

    public String getName() { return name; }
    public int getAge() { return age; }
    public double getGpa() { return gpa; }

    @Override
    public String toString() {
        return name + "(age=" + age + ", gpa=" + gpa + ")";
    }
}

// Usage
import java.util.*;

public class ComparableDemo {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(Arrays.asList(
            new Student("Charlie", 20, 3.5),
            new Student("Alice", 22, 3.8),
            new Student("Bob", 21, 3.6),
            new Student("Alice", 19, 3.9)
        ));
        Collections.sort(students);
        students.forEach(s -> System.out.println(s));
        // Alice(19, 3.9), Alice(22, 3.8), Bob(21, 3.6), Charlie(20, 3.5)
    }
}

Key points:

  • compareTo() is called by Collections.sort(), TreeSet, TreeMap
  • Must be consistent with equals() for correct Set/Map behavior
  • Use Integer.compare(), Double.compare() instead of subtraction (avoids overflow)

Comparator

Comparator

Comparator<T> defines custom ordering separate from the class's natural ordering. It has a single method compare(T o1, T o2).

import java.util.*;

public class ComparatorDemo {
    static Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());

    public static void main(String[] args) {
        List<String> names = new ArrayList<>(Arrays.asList(
            "Charlie", "Alice", "Bob", "David"
        ));

        names.sort(byLength);
        System.out.println("By length: " + names); // [Bob, Alice, David, Charlie]

        names.sort(Comparator.comparingInt(String::length)
                .thenComparing(Comparator.naturalOrder()));
        System.out.println("By length then alpha: " + names);

        names.sort(Comparator.comparingInt(String::length).reversed());
        System.out.println("By length reversed: " + names);
    }
}

Key points:

  • Comparator is external to the class — doesn't modify it
  • Use when you need multiple sort orders
  • compare(o1, o2) vs o1.compareTo(o2)

Lambda Syntax

Lambda Comparators (Java 8+)

Java 8 introduced lambda expressions and method references, making Comparator creation concise.

import java.util.*;

public class LambdaComparatorDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");

        names.sort((a, b) -> a.length() - b.length());
        System.out.println("By length: " + names);

        names.sort(Comparator.comparingInt(String::length));
        System.out.println("By length (method ref): " + names);

        names.sort(Comparator.comparingInt(String::length).reversed());
        System.out.println("By length desc: " + names);

        List<String> withNulls = Arrays.asList("b", null, "a", null, "c");
        withNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println("Nulls first: " + withNulls);

        withNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
        System.out.println("Nulls last: " + withNulls);

        List<String> mixed = Arrays.asList("Banana", "apple", "Cherry");
        mixed.sort(String.CASE_INSENSITIVE_ORDER);
        System.out.println("Case insensitive: " + mixed);
    }
}

Lambda patterns:

  • (a, b) -> a.length() - b.length() — basic lambda
  • Comparator.comparingInt(String::length) — method reference
  • .thenComparing() — chain conditions
  • .reversed() — reverse order
  • Comparator.nullsFirst/Last() — handle nulls

Chaining

Comparator Chaining

Java 8+ provides chaining methods to combine multiple sorting criteria.

import java.util.*;

public class ChainingDemo {
    static class Employee {
        String name;
        String department;
        int age;
        double salary;

        Employee(String name, String dept, int age, double salary) {
            this.name = name; this.department = dept; this.age = age; this.salary = salary;
        }

        @Override
        public String toString() {
            return name + "(" + department + ", " + age + ", $" + salary + ")";
        }
    }

    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>(Arrays.asList(
            new Employee("Alice", "Engineering", 30, 95000),
            new Employee("Bob", "Marketing", 25, 70000),
            new Employee("Charlie", "Engineering", 35, 110000),
            new Employee("Diana", "Marketing", 28, 80000),
            new Employee("Eve", "Engineering", 25, 90000)
        ));

        employees.sort(Comparator.comparing((Employee e) -> e.department)
                .thenComparing(Comparator.comparingDouble((Employee e) -> e.salary).reversed()));
        System.out.println("By dept, then salary desc:");
        employees.forEach(e -> System.out.println("  " + e));

        employees.sort(Comparator.comparingInt((Employee e) -> e.age)
                .thenComparing((Employee e) -> e.name));
        System.out.println("\nBy age, then name:");
        employees.forEach(e -> System.out.println("  " + e));

        Comparator<Employee> byDeptThenName =
            Comparator.comparing((Employee e) -> e.department)
                      .thenComparing((Employee e) -> e.name);
        employees.sort(byDeptThenName);
        System.out.println("\nBy dept, then name:");
        employees.forEach(e -> System.out.println("  " + e));
    }
}

Chaining methods:

  • thenComparing(comparator) — add secondary sort
  • thenComparingInt/Double/Long(keyExtractor) — primitive-specific
  • reversed() — reverse the comparator

Best practice: Use Comparator.comparing() with method references for clean, type-safe comparators.

Practice Problems

0/3solved
Sort Objects by Multiple Fields

Create a Person class implementing Comparable with natural ordering by name. Then sort a list by age descending, then by name ascending using Comparator.

Solution
import java.util.*;

public class Person implements Comparable<Person> {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }

    public static void sortByAgeThenName(List<Person> people) {
        people.sort(Comparator.comparingInt((Person p) -> p.age).reversed()
                .thenComparing(p -> p.name));
    }

    @Override
    public String toString() { return name + "(" + age + ")"; }
}
Custom Sorting for Strings

Sort a list of strings first by length (shortest first), then alphabetically for strings of the same length.

Solution
import java.util.*;

public class StringSorter {
    public static List<String> sortByLengthThenAlpha(List<String> strings) {
        List<String> result = new ArrayList<>(strings);
        result.sort(Comparator.comparingInt(String::length)
                .thenComparing(Comparator.naturalOrder()));
        return result;
    }
}
Sort Map by Values

Write a method that takes a Map<String, Integer> and returns a new LinkedHashMap sorted by values in descending order.

Solution
import java.util.*;

public class MapSorter {
    public static LinkedHashMap<String, Integer> sortByValueDesc(Map<String, Integer> map) {
        return map.entrySet().stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
            .collect(LinkedHashMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), LinkedHashMap::putAll);
    }
}

Quiz

1. What is the difference between Comparable and Comparator?

Question 1 options

2. What method does Comparable define?

Question 2 options

3. How do you create a max-heap Comparator for integers?

Question 3 options

4. What does Comparator.thenComparing() do?

Question 4 options

Flashcards

Question

What is the contract of compareTo()?

Answer

Must be consistent with equals(), transitive, and antisymmetric. Returns negative if this < other, zero if equal, positive if this > other.

Question

How do you create a Comparator with a lambda?

Answer

(a, b) -> Integer.compare(a.field, b.field) or Comparator.comparingInt(ClassName::getField). Use .thenComparing() to chain and .reversed() to reverse.

Question

When should you use Comparable vs Comparator?

Answer

Comparable for the single natural ordering defined in the class. Comparator for multiple custom orderings or when you can't modify the class.

Question

How do you handle null values in Comparator?

Answer

Use Comparator.nullsFirst(comparator) to put nulls first, or Comparator.nullsLast(comparator) to put nulls last.

Question

What is Comparable and Comparator?

Answer

Comparable and Comparator is a key concept in Java programming.

Revision Notes

Key Takeaways

  • 1.Comparable defines natural ordering inside the class
  • 2.Comparator defines custom ordering externally
  • 3.Use Integer.compare() not subtraction in compareTo
  • 4.Chain with thenComparing() for multi-field sorting
  • 5.Be consistent with equals() when implementing Comparable

Interview Tips

  • Explain the difference between Comparable and Comparator with examples
  • Demonstrate lambda comparators: Comparator.comparingInt().thenComparing()
  • Discuss why compareTo must be consistent with equals
  • Know how to sort a Map by values using streams and Comparators

Cheat Sheet

Comparable vs Comparator

Comparable

  • Interface in the class
  • compareTo(T o) method
  • Natural ordering
  • Must be consistent with equals()

Comparator

  • Separate object
  • compare(T o1, T o2) method
  • Custom ordering
  • Multiple comparators per class

Lambda Comparators

  • Comparator.comparingInt(Class::field)
  • .thenComparing() for chaining
  • .reversed() for reverse
  • nullsFirst/nullsLast for nulls