The Comparator Interface

Introduction

Comparator interface is used to define custom sorting order for objects. It provides flexibility to sort objects in different ways.


Comparable vs Comparator

FeatureComparableComparator
Packagejava.langjava.util
MethodcompareTo(T o)compare(T o1, T o2)
Sorting logicInside classExternal class
ModificationModify original classSeparate class
Multiple sortsOne way onlyMultiple ways
UsageCollections.sort(list)Collections.sort(list, comparator)

Basic Syntax

import java.util.Comparator;

class MyComparator implements Comparator<Type> {
    @Override
    public int compare(Type o1, Type o2) {
        // Return negative if o1 < o2
        // Return 0 if o1 == o2
        // Return positive if o1 > o2
    }
}

Simple Example

import java.util.*;

class Student {
    String name;
    int marks;

    Student(String name, int marks) {
        this.name = name;
        this.marks = marks;
    }

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

// Comparator for sorting by marks
class MarksComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.marks - s2.marks;  // Ascending order
    }
}

// Comparator for sorting by name
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.name.compareTo(s2.name);  // Alphabetical
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("John", 85));
        students.add(new Student("Alice", 92));
        students.add(new Student("Bob", 78));

        // Sort by marks
        Collections.sort(students, new MarksComparator());
        System.out.println("By marks: " + students);

        // Sort by name
        Collections.sort(students, new NameComparator());
        System.out.println("By name: " + students);
    }
}

Return Value Rules

compare(o1, o2) returns:

• Negative number (e.g., -1): o1 comes before o2 (o1 < o2)
Zero (0):                    o1 equals o2 (o1 == o2)
• Positive number (e.g., +1):  o1 comes after o2 (o1 > o2)

Ascending vs Descending

import java.util.*;

class AscendingComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer a, Integer b) {
        return a - b;  // Ascending: smaller first
    }
}

class DescendingComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer a, Integer b) {
        return b - a;  // Descending: larger first
    }
}

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

        Collections.sort(numbers, new AscendingComparator());
        System.out.println("Ascending: " + numbers);  // [1, 2, 5, 8, 9]

        Collections.sort(numbers, new DescendingComparator());
        System.out.println("Descending: " + numbers);  // [9, 8, 5, 2, 1]
    }
}

Lambda Expression (Java 8+)

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

        // Ascending using lambda
        Collections.sort(numbers, (a, b) -> a - b);
        System.out.println("Ascending: " + numbers);

        // Descending using lambda
        Collections.sort(numbers, (a, b) -> b - a);
        System.out.println("Descending: " + numbers);
    }
}

Multiple Sorting Criteria

import java.util.*;

class Employee {
    String name;
    int salary;
    int age;

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

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

// Sort by salary, then by age if salary is same
class EmployeeComparator implements Comparator<Employee> {
    @Override
    public int compare(Employee e1, Employee e2) {
        // First compare by salary
        int salaryCompare = e1.salary - e2.salary;
        if (salaryCompare != 0) {
            return salaryCompare;
        }

        // If salary same, compare by age
        return e1.age - e2.age;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("John", 50000, 30));
        employees.add(new Employee("Alice", 60000, 25));
        employees.add(new Employee("Bob", 50000, 28));

        Collections.sort(employees, new EmployeeComparator());
        System.out.println(employees);
        // Bob (50000, 28)
        // John (50000, 30)
        // Alice (60000, 25)
    }
}

String Comparison

import java.util.*;

class Student {
    String name;

    Student(String name) {
        this.name = name;
    }

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

// Case-sensitive sorting
class CaseSensitiveComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.name.compareTo(s2.name);
    }
}

// Case-insensitive sorting
class CaseInsensitiveComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.name.compareToIgnoreCase(s2.name);
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
            new Student("alice"),
            new Student("Bob"),
            new Student("Charlie")
        );

        Collections.sort(students, new CaseSensitiveComparator());
        System.out.println("Case-sensitive: " + students);
        // [Bob, Charlie, alice]

        Collections.sort(students, new CaseInsensitiveComparator());
        System.out.println("Case-insensitive: " + students);
        // [alice, Bob, Charlie]
    }
}

Comparator.comparing() (Java 8+)

import java.util.*;

class Person {
    String name;
    int age;

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

    String getName() { return name; }
    int getAge() { return age; }

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

public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("John", 30),
            new Person("Alice", 25),
            new Person("Bob", 35)
        );

        // Sort by name
        people.sort(Comparator.comparing(Person::getName));
        System.out.println("By name: " + people);

        // Sort by age
        people.sort(Comparator.comparing(Person::getAge));
        System.out.println("By age: " + people);

        // Sort by age descending
        people.sort(Comparator.comparing(Person::getAge).reversed());
        System.out.println("By age (desc): " + people);
    }
}

Chaining Comparators

import java.util.*;

class Product {
    String name;
    double price;
    int rating;

    Product(String name, double price, int rating) {
        this.name = name;
        this.price = price;
        this.rating = rating;
    }

    @Override
    public String toString() {
        return name + " ($" + price + ", " + rating + "★)";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product("Laptop", 800, 4),
            new Product("Phone", 500, 5),
            new Product("Tablet", 500, 4)
        );

        // Sort by price, then by rating
        products.sort(
            Comparator.comparing((Product p) -> p.price)
                     .thenComparing(p -> p.rating)
        );

        System.out.println(products);
        // Tablet ($500.0, 4★)
        // Phone ($500.0, 5★)
        // Laptop ($800.0, 4★)
    }
}

Real-World Example: Employee Management

import java.util.*;

class Employee {
    int id;
    String name;
    String department;
    double salary;

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

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

class EmployeeComparators {
    // Sort by ID
    static class ByID implements Comparator<Employee> {
        public int compare(Employee e1, Employee e2) {
            return e1.id - e2.id;
        }
    }

    // Sort by name
    static class ByName implements Comparator<Employee> {
        public int compare(Employee e1, Employee e2) {
            return e1.name.compareTo(e2.name);
        }
    }

    // Sort by department, then salary
    static class ByDepartmentAndSalary implements Comparator<Employee> {
        public int compare(Employee e1, Employee e2) {
            int deptCompare = e1.department.compareTo(e2.department);
            if (deptCompare != 0) {
                return deptCompare;
            }
            return Double.compare(e2.salary, e1.salary);  // Descending salary
        }
    }

    // Sort by salary (descending)
    static class BySalaryDesc implements Comparator<Employee> {
        public int compare(Employee e1, Employee e2) {
            return Double.compare(e2.salary, e1.salary);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee(3, "John", "IT", 60000));
        employees.add(new Employee(1, "Alice", "HR", 50000));
        employees.add(new Employee(2, "Bob", "IT", 65000));
        employees.add(new Employee(4, "Carol", "HR", 55000));

        System.out.println("Original:");
        employees.forEach(System.out::println);

        System.out.println("\nBy ID:");
        employees.sort(new EmployeeComparators.ByID());
        employees.forEach(System.out::println);

        System.out.println("\nBy Name:");
        employees.sort(new EmployeeComparators.ByName());
        employees.forEach(System.out::println);

        System.out.println("\nBy Salary (Highest first):");
        employees.sort(new EmployeeComparators.BySalaryDesc());
        employees.forEach(System.out::println);

        System.out.println("\nBy Department, then Salary:");
        employees.sort(new EmployeeComparators.ByDepartmentAndSalary());
        employees.forEach(System.out::println);
    }
}

Null Handling

import java.util.*;

class NullSafeComparator implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {
        // Handle nulls
        if (s1 == null && s2 == null) return 0;
        if (s1 == null) return -1;  // null comes first
        if (s2 == null) return 1;

        return s1.compareTo(s2);
    }
}

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

        names.sort(new NullSafeComparator());
        System.out.println(names);  // [null, null, Alice, Bob, John]

        // Java 8+ nullsFirst/nullsLast
        names.sort(Comparator.nullsLast(Comparator.naturalOrder()));
        System.out.println(names);  // [Alice, Bob, John, null, null]
    }
}

Anonymous Inner Class

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

        // Anonymous comparator
        Collections.sort(numbers, new Comparator<Integer>() {
            @Override
            public int compare(Integer a, Integer b) {
                return b - a;  // Descending
            }
        });

        System.out.println(numbers);  // [9, 8, 5, 2, 1]
    }
}

TreeSet with Comparator

import java.util.*;

class Student {
    String name;
    int marks;

    Student(String name, int marks) {
        this.name = name;
        this.marks = marks;
    }

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

public class Main {
    public static void main(String[] args) {
        // TreeSet with custom comparator (sorted by marks)
        Set<Student> students = new TreeSet<>(
            (s1, s2) -> s1.marks - s2.marks
        );

        students.add(new Student("John", 85));
        students.add(new Student("Alice", 92));
        students.add(new Student("Bob", 78));

        System.out.println(students);
        // [Bob: 78, John: 85, Alice: 92]
    }
}

Common Patterns

1. Reverse Order:

Collections.sort(list, Collections.reverseOrder());
// Or
Collections.sort(list, Comparator.reverseOrder());

2. Natural Order:

Collections.sort(list, Comparator.naturalOrder());

3. Reversed:

Comparator<String> comp = Comparator.naturalOrder();
list.sort(comp.reversed());

Quick Reference

import java.util.Comparator;

// Basic implementation
class MyComparator implements Comparator<Type> {
    public int compare(Type o1, Type o2) {
        return o1.field - o2.field;  // Ascending
        // return o2.field - o1.field;  // Descending
    }
}

// Usage
Collections.sort(list, new MyComparator());

// Lambda (Java 8+)
list.sort((a, b) -> a.field - b.field);

// Method reference
list.sort(Comparator.comparing(Type::getField));

// Multiple criteria
list.sort(Comparator.comparing(Type::getField1)
                    .thenComparing(Type::getField2));

// Reverse
list.sort(Comparator.comparing(Type::getField).reversed());

Common Mistakes

// ✗ Wrong - may cause overflow
return o1.largeNumber - o2.largeNumber;

// ✓ Correct - use compare methods
return Integer.compare(o1.number, o2.number);
return Double.compare(o1.value, o2.value);
return Long.compare(o1.longValue, o2.longValue);

// ✗ Wrong - null pointer exception
return o1.name.compareTo(o2.name);

// ✓ Correct - handle nulls
if (o1.name == null) return -1;
if (o2.name == null) return 1;
return o1.name.compareTo(o2.name);

Exam Tips

Remember:

  1. Comparator for custom sorting
  2. compare(o1, o2) method
  3. Negative: o1 < o2 (o1 first)
  4. Zero: o1 == o2 (equal)
  5. Positive: o1 > o2 (o2 first)
  6. Ascending: return a - b
  7. Descending: return b - a
  8. Lambda: (a, b) -> expression
  9. comparing(): Comparator.comparing()
  10. Multiple criteria with thenComparing()

Common Questions:

  • What is Comparator interface?
  • Comparable vs Comparator?
  • How to sort in descending order?
  • Return values of compare()?
  • Lambda with Comparator?
  • Multiple sorting criteria?
  • TreeSet with Comparator?
  • Null handling in Comparator?
  • Comparator.comparing() usage?
  • When to use Comparator?