When sorting objects by different criteria like name, age, or price, or when sorting classes you cannot modify, you need flexible comparison logic. The Comparator interface defines custom ordering strategies separate from the objects being compared.

Basic Comparator

Create simple comparators using lambdas.

Basic.java
// Basic Comparator examples

import java.util.*;

public class Basic {
    public static void main(String[] args) {
        System.out.println("String length comparator:");

        List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");

        System.out.println("Original: " + words);

        // Sort by length
        Comparator<String> byLength = new Comparator<String>() {
            public int compare(String a, String b) {
                return a.length() - b.length();
            }
        };

        words.sort(byLength);
        System.out.println("By length: " + words);
        System.out.println("\nLambda syntax:");

        List<String> fruits = Arrays.asList("strawberry", "fig", "apple", "pear");

        System.out.println("Original: " + fruits);

        // Lambda comparator
        fruits.sort((a, b) -> a.length() - b.length());
        System.out.println("By length: " + fruits);

        // Reverse length
        fruits.sort((a, b) -> b.length() - a.length());
        System.out.println("Reverse length: " + fruits);
        System.out.println("\nCase-insensitive:");

        List<String> names = Arrays.asList("Alice", "bob", "Charlie", "david");

        System.out.println("Original: " + names);

        names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));
        System.out.println("Case-insensitive: " + names);
        System.out.println("\nNumbers:");

        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

        System.out.println("Original: " + numbers);

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

        // Descending
        numbers.sort((a, b) -> b - a);
        System.out.println("Descending: " + numbers);
        System.out.println("\nCustom objects:");

        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

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

        // Sort by age
        people.sort((a, b) -> a.age - b.age);
        System.out.println("\nBy age:");
        people.forEach(System.out::println);

        // Sort by name
        people.sort((a, b) -> a.name.compareTo(b.name));
        System.out.println("\nBy name:");
        people.forEach(System.out::println);
        System.out.println("\nComparator variable:");

        Comparator<Person> byAge = (a, b) -> a.age - b.age;
        Comparator<Person> byName = (a, b) -> a.name.compareTo(b.name);

        List<Person> staff = Arrays.asList(
            new Person("David", 28),
            new Person("Alice", 32),
            new Person("Bob", 28)
        );

        System.out.println("Sort by age:");
        staff.sort(byAge);
        staff.forEach(System.out::println);

        System.out.println("\nSort by name:");
        staff.sort(byName);
        staff.forEach(System.out::println);
        System.out.println("\nWith Collections:");

        List<String> items = Arrays.asList("Zebra", "Apple", "Mango", "Banana");

        System.out.println("Original: " + items);

        Collections.sort(items, (a, b) -> a.compareTo(b));
        System.out.println("Sorted: " + items);

        Collections.sort(items, (a, b) -> b.compareTo(a));
        System.out.println("Reversed: " + items);
        System.out.println("\nMin/Max:");

        int extraValue = ;
        List<Integer> values = Arrays.asList(5, 2, 8, 1, 9, extraValue);

        int min = Collections.min(values, (a, b) -> a - b);
        int max = Collections.max(values, (a, b) -> a - b);

        System.out.println("Values: " + values);
        System.out.println("Min: " + min);
        System.out.println("Max: " + max);
    }

    static class Person {
        String name;
        int age;

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

        @Override
        public String toString() {
            return name + " (" + age + ")";
        }
    }
}
// Basic Comparator examples

import java.util.*;

public class Basic {
    public static void main(String[] args) {
        System.out.println("String length comparator:");

        List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");

        System.out.println("Original: " + words);

        // Sort by length
        Comparator<String> byLength = new Comparator<String>() {
            public int compare(String a, String b) {
                return a.length() - b.length();
            }
        };

        words.sort(byLength);
        System.out.println("By length: " + words);
        System.out.println("\nLambda syntax:");

        List<String> fruits = Arrays.asList("strawberry", "fig", "apple", "pear");

        System.out.println("Original: " + fruits);

        // Lambda comparator
        fruits.sort((a, b) -> a.length() - b.length());
        System.out.println("By length: " + fruits);

        // Reverse length
        fruits.sort((a, b) -> b.length() - a.length());
        System.out.println("Reverse length: " + fruits);
        System.out.println("\nCase-insensitive:");

        List<String> names = Arrays.asList("Alice", "bob", "Charlie", "david");

        System.out.println("Original: " + names);

        names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));
        System.out.println("Case-insensitive: " + names);
        System.out.println("\nNumbers:");

        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

        System.out.println("Original: " + numbers);

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

        // Descending
        numbers.sort((a, b) -> b - a);
        System.out.println("Descending: " + numbers);
        System.out.println("\nCustom objects:");

        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

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

        // Sort by age
        people.sort((a, b) -> a.age - b.age);
        System.out.println("\nBy age:");
        people.forEach(System.out::println);

        // Sort by name
        people.sort((a, b) -> a.name.compareTo(b.name));
        System.out.println("\nBy name:");
        people.forEach(System.out::println);
        System.out.println("\nComparator variable:");

        Comparator<Person> byAge = (a, b) -> a.age - b.age;
        Comparator<Person> byName = (a, b) -> a.name.compareTo(b.name);

        List<Person> staff = Arrays.asList(
            new Person("David", 28),
            new Person("Alice", 32),
            new Person("Bob", 28)
        );

        System.out.println("Sort by age:");
        staff.sort(byAge);
        staff.forEach(System.out::println);

        System.out.println("\nSort by name:");
        staff.sort(byName);
        staff.forEach(System.out::println);
        System.out.println("\nWith Collections:");

        List<String> items = Arrays.asList("Zebra", "Apple", "Mango", "Banana");

        System.out.println("Original: " + items);

        Collections.sort(items, (a, b) -> a.compareTo(b));
        System.out.println("Sorted: " + items);

        Collections.sort(items, (a, b) -> b.compareTo(a));
        System.out.println("Reversed: " + items);
        System.out.println("\nMin/Max:");

        int extraValue = ;
        List<Integer> values = Arrays.asList(5, 2, 8, 1, 9, extraValue);

        int min = Collections.min(values, (a, b) -> a - b);
        int max = Collections.max(values, (a, b) -> a - b);

        System.out.println("Values: " + values);
        System.out.println("Min: " + min);
        System.out.println("Max: " + max);
    }

    static class Person {
        String name;
        int age;

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

        @Override
        public String toString() {
            return name + " (" + age + ")";
        }
    }
}
// Basic Comparator examples

import java.util.*;

public class Basic {
    public static void main(String[] args) {
        System.out.println("String length comparator:");

        List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");

        System.out.println("Original: " + words);

        // Sort by length
        Comparator<String> byLength = new Comparator<String>() {
            public int compare(String a, String b) {
                return a.length() - b.length();
            }
        };

        words.sort(byLength);
        System.out.println("By length: " + words);
        System.out.println("\nLambda syntax:");

        List<String> fruits = Arrays.asList("strawberry", "fig", "apple", "pear");

        System.out.println("Original: " + fruits);

        // Lambda comparator
        fruits.sort((a, b) -> a.length() - b.length());
        System.out.println("By length: " + fruits);

        // Reverse length
        fruits.sort((a, b) -> b.length() - a.length());
        System.out.println("Reverse length: " + fruits);
        System.out.println("\nCase-insensitive:");

        List<String> names = Arrays.asList("Alice", "bob", "Charlie", "david");

        System.out.println("Original: " + names);

        names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));
        System.out.println("Case-insensitive: " + names);
        System.out.println("\nNumbers:");

        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);

        System.out.println("Original: " + numbers);

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

        // Descending
        numbers.sort((a, b) -> b - a);
        System.out.println("Descending: " + numbers);
        System.out.println("\nCustom objects:");

        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

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

        // Sort by age
        people.sort((a, b) -> a.age - b.age);
        System.out.println("\nBy age:");
        people.forEach(System.out::println);

        // Sort by name
        people.sort((a, b) -> a.name.compareTo(b.name));
        System.out.println("\nBy name:");
        people.forEach(System.out::println);
        System.out.println("\nComparator variable:");

        Comparator<Person> byAge = (a, b) -> a.age - b.age;
        Comparator<Person> byName = (a, b) -> a.name.compareTo(b.name);

        List<Person> staff = Arrays.asList(
            new Person("David", 28),
            new Person("Alice", 32),
            new Person("Bob", 28)
        );

        System.out.println("Sort by age:");
        staff.sort(byAge);
        staff.forEach(System.out::println);

        System.out.println("\nSort by name:");
        staff.sort(byName);
        staff.forEach(System.out::println);
        System.out.println("\nWith Collections:");

        List<String> items = Arrays.asList("Zebra", "Apple", "Mango", "Banana");

        System.out.println("Original: " + items);

        Collections.sort(items, (a, b) -> a.compareTo(b));
        System.out.println("Sorted: " + items);

        Collections.sort(items, (a, b) -> b.compareTo(a));
        System.out.println("Reversed: " + items);
        System.out.println("\nMin/Max:");

        int extraValue = ;
        List<Integer> values = Arrays.asList(5, 2, 8, 1, 9, extraValue);

        int min = Collections.min(values, (a, b) -> a - b);
        int max = Collections.max(values, (a, b) -> a - b);

        System.out.println("Values: " + values);
        System.out.println("Min: " + min);
        System.out.println("Max: " + max);
    }

    static class Person {
        String name;
        int age;

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

        @Override
        public String toString() {
            return name + " (" + age + ")";
        }
    }
}
Comparator A functional interface defining comparison logic externally, allowing multiple sort orders for the same class.
Comparison contract Return negative if first is less, zero if equal, positive if first is greater. Avoid subtraction for int comparison due to overflow.

Using Comparator.comparing()

Create comparators declaratively from method references.

Comparing.java
// Comparator.comparing examples

import java.util.*;
import java.util.function.Function;

public class Comparing {
    public static void main(String[] args) {
        System.out.println("Basic comparing:");

        List<Person> people = Arrays.asList(
            new Person("Alice", 30, 75000),
            new Person("Bob", 25, 60000),
            new Person("Charlie", 35, 80000)
        );

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

        // Sort by age using method reference
        people.sort(Comparator.comparing(Person::getAge));
        System.out.println("\nBy age:");
        people.forEach(System.out::println);
        System.out.println("\nDifferent keys:");

        List<Person> staff = Arrays.asList(
            new Person("David", 28, 70000),
            new Person("Alice", 32, 75000),
            new Person("Bob", 28, 65000)
        );

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

        // Sort by salary
        staff.sort(Comparator.comparing(Person::getSalary));
        System.out.println("\nBy salary:");
        staff.forEach(System.out::println);
        System.out.println("\nLambda extractor:");

        List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");

        // Sort by length (lambda)
        words.sort(Comparator.comparing(s -> s.length()));
        System.out.println("By length: " + words);

        // Sort by last character
        words.sort(Comparator.comparing(s -> s.charAt(s.length() - 1)));
        System.out.println("By last char: " + words);
        System.out.println("\nComparingInt/Long/Double:");

        List<Product> products = Arrays.asList(
            new Product("Widget", 29.99, 100),
            new Product("Gadget", 49.99, 50),
            new Product("Tool", 19.99, 200)
        );

        // comparingInt for primitives (more efficient)
        products.sort(Comparator.comparingInt(Product::getQuantity));
        System.out.println("By quantity:");
        products.forEach(System.out::println);

        // comparingDouble for prices
        products.sort(Comparator.comparingDouble(Product::getPrice));
        System.out.println("\nBy price:");
        products.forEach(System.out::println);
        System.out.println("\nChained comparators:");

        List<Person> employees = Arrays.asList(
            new Person("Alice", 30, 70000),
            new Person("Bob", 25, 70000),
            new Person("Charlie", 30, 65000)
        );

        // Sort by salary, then by age
        employees.sort(
            Comparator.comparing(Person::getSalary)
                      .thenComparing(Person::getAge)
        );

        System.out.println("By salary, then age:");
        employees.forEach(System.out::println);
        System.out.println("\nThenComparing variants:");

        List<Person> team = Arrays.asList(
            new Person("Alice", 30, 70000),
            new Person("Alice", 25, 60000),
            new Person("Bob", 30, 70000)
        );

        // By name, then age
        team.sort(
            Comparator.comparing(Person::getName)
                      .thenComparingInt(Person::getAge)
        );

        System.out.println("By name, then age:");
        team.forEach(System.out::println);

        // Three levels
        team.sort(
            Comparator.comparing(Person::getName)
                      .thenComparingInt(Person::getAge)
                      .thenComparingDouble(Person::getSalary)
        );

        System.out.println("\nBy name, age, salary:");
        team.forEach(System.out::println);
        System.out.println("\nReverse order:");

        List<Product> items = Arrays.asList(
            new Product("A", 20.0, 100),
            new Product("B", 30.0, 50),
            new Product("C", 10.0, 200)
        );

        // Descending price
        items.sort(Comparator.comparing(Product::getPrice).reversed());
        System.out.println("Price descending:");
        items.forEach(System.out::println);
        System.out.println("\nCustom extraction:");

        List<Employee> workers = Arrays.asList(
            new Employee("Alice", "Engineering", 30),
            new Employee("Bob", "Sales", 25),
            new Employee("Charlie", "Engineering", 35)
        );

        // Sort by department length, then name
        workers.sort(
            Comparator.comparing((Employee e) -> e.department.length())
                      .thenComparing(Employee::getName)
        );

        System.out.println("By dept length, then name:");
        workers.forEach(System.out::println);
        System.out.println("\nStream sorting:");

        List<Person> sorted = Arrays.asList(
            new Person("Charlie", 35, 80000),
            new Person("Alice", 30, 75000),
            new Person("Bob", 25, 60000)
        )
        .stream()
        .sorted(Comparator.comparing(Person::getAge))
        .toList();

        System.out.println("Sorted stream:");
        sorted.forEach(System.out::println);
    }

    static class Person {
        private String name;
        private int age;
        private double salary;

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

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

        @Override
        public String toString() {
            return String.format("%s (age=%d, salary=$%.0f)",
                name, age, salary);
        }
    }

    static class Product {
        private String name;
        private double price;
        private int quantity;

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

        double getPrice() { return price; }
        int getQuantity() { return quantity; }

        @Override
        public String toString() {
            return String.format("%s: $%.2f (qty=%d)",
                name, price, quantity);
        }
    }

    static class Employee {
        private String name;
        private String department;
        private int age;

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

        String getName() { return name; }

        @Override
        public String toString() {
            return String.format("%s (%s, %d)", name, department, age);
        }
    }
}
Key extractor Comparator.comparing() takes a function that extracts the sort key, making comparator creation concise and readable.

Chained Comparators

Sort by multiple fields with thenComparing().

Chained.java
// Chained comparators examples

import java.util.*;

public class Chained {
    public static void main(String[] args) {
        System.out.println("Two-level sort:");

        List<Student> students = Arrays.asList(
            new Student("Alice", "A", 85),
            new Student("Bob", "B", 92),
            new Student("Charlie", "A", 78),
            new Student("David", "B", 85)
        );

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

        // Sort by grade, then score
        students.sort(
            Comparator.comparing(Student::getGrade)
                      .thenComparingInt(Student::getScore)
        );

        System.out.println("\nBy grade, then score:");
        students.forEach(System.out::println);
        System.out.println("\nThree-level sort:");

        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "Engineering", 30, 75000),
            new Employee("Bob", "Engineering", 30, 70000),
            new Employee("Charlie", "Sales", 25, 60000),
            new Employee("David", "Engineering", 25, 65000)
        );

        // Sort by department, age, then salary
        employees.sort(
            Comparator.comparing(Employee::getDepartment)
                      .thenComparingInt(Employee::getAge)
                      .thenComparingDouble(Employee::getSalary)
        );

        System.out.println("By dept, age, salary:");
        employees.forEach(System.out::println);
        System.out.println("\nMixed ascending/descending:");

        List<Product> products = Arrays.asList(
            new Product("Widget", "A", 29.99, 100),
            new Product("Gadget", "B", 49.99, 50),
            new Product("Tool", "A", 19.99, 200),
            new Product("Device", "B", 39.99, 150)
        );

        // Category ascending, price descending
        products.sort(
            Comparator.comparing(Product::getCategory)
                      .thenComparing(
                          Comparator.comparingDouble(Product::getPrice).reversed()
                      )
        );

        System.out.println("Category asc, price desc:");
        products.forEach(System.out::println);
        System.out.println("\nReverse entire chain:");

        List<Student> roster = Arrays.asList(
            new Student("Alice", "A", 85),
            new Student("Bob", "B", 92),
            new Student("Charlie", "A", 95)
        );

        // Reverse the entire sort
        roster.sort(
            Comparator.comparing(Student::getGrade)
                      .thenComparingInt(Student::getScore)
                      .reversed()
        );

        System.out.println("Reversed grade/score:");
        roster.forEach(System.out::println);
        System.out.println("\nCustom order:");

        Map<String, Integer> priorityOrder = Map.of(
            "High", 1,
            "Medium", 2,
            "Low", 3
        );

        List<Task> tasks = Arrays.asList(
            new Task("Fix bug", "High", 2),
            new Task("Write docs", "Low", 1),
            new Task("Review code", "Medium", 3),
            new Task("Testing", "High", 1)
        );

        // Sort by priority order, then days
        tasks.sort(
            Comparator.comparing((Task t) -> priorityOrder.get(t.getPriority()))
                      .thenComparingInt(Task::getDays)
        );

        System.out.println("By priority, then days:");
        tasks.forEach(System.out::println);
        System.out.println("\nNull handling:");

        List<Person> people = Arrays.asList(
            new Person("Alice", "Engineering"),
            new Person("Bob", null),
            new Person("Charlie", "Sales"),
            new Person("David", null)
        );

        // Nulls last, then alphabetic
        people.sort(
            Comparator.comparing(
                Person::getDepartment,
                Comparator.nullsLast(String::compareTo)
            )
            .thenComparing(Person::getName)
        );

        System.out.println("Dept (nulls last), then name:");
        people.forEach(System.out::println);
        System.out.println("\nStable sort:");

        List<Record> records = Arrays.asList(
            new Record(1, "A", 100),
            new Record(2, "B", 100),
            new Record(3, "A", 200),
            new Record(4, "B", 100)
        );

        System.out.println("Original order:");
        records.forEach(System.out::println);

        // Sort by category only - original order preserved within category
        records.sort(Comparator.comparing(Record::getCategory));

        System.out.println("\nBy category (stable):");
        records.forEach(System.out::println);
        System.out.println("\nPerformance optimization:");

        // Reuse comparators
        Comparator<Employee> byDept = Comparator.comparing(Employee::getDepartment);
        Comparator<Employee> byAge = Comparator.comparingInt(Employee::getAge);
        Comparator<Employee> bySalary = Comparator.comparingDouble(Employee::getSalary);

        Comparator<Employee> combined = byDept.thenComparing(byAge).thenComparing(bySalary);

        employees.sort(combined);

        System.out.println("Using reusable comparators:");
        employees.forEach(System.out::println);
    }

    static class Student {
        private String name;
        private String grade;
        private int score;

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

        String getGrade() { return grade; }
        int getScore() { return score; }

        @Override
        public String toString() {
            return String.format("%s: %s (%d)", name, grade, score);
        }
    }

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

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

        String getDepartment() { return department; }
        int getAge() { return age; }
        double getSalary() { return salary; }

        @Override
        public String toString() {
            return String.format("%s (%s, %d, $%.0f)",
                name, department, age, salary);
        }
    }

    static class Product {
        private String name;
        private String category;
        private double price;
        private int quantity;

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

        String getCategory() { return category; }
        double getPrice() { return price; }

        @Override
        public String toString() {
            return String.format("%s [%s]: $%.2f", name, category, price);
        }
    }

    static class Task {
        private String title;
        private String priority;
        private int days;

        Task(String title, String priority, int days) {
            this.title = title;
            this.priority = priority;
            this.days = days;
        }

        String getPriority() { return priority; }
        int getDays() { return days; }

        @Override
        public String toString() {
            return String.format("%s [%s, %dd]", title, priority, days);
        }
    }

    static class Person {
        private String name;
        private String department;

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

        String getName() { return name; }
        String getDepartment() { return department; }

        @Override
        public String toString() {
            return String.format("%s (%s)", name, department);
        }
    }

    static class Record {
        private int id;
        private String category;
        private int value;

        Record(int id, String category, int value) {
            this.id = id;
            this.category = category;
            this.value = value;
        }

        String getCategory() { return category; }

        @Override
        public String toString() {
            return String.format("#%d: %s=%d", id, category, value);
        }
    }
}
Secondary sort Use thenComparing() to break ties, like sorting by last name then first name.

Reverse Ordering

Invert sort order easily.

Reverse.java
// Reverse order comparators

import java.util.*;

public class Reverse {
    public static void main(String[] args) {
        System.out.println("Basic reversed:");

        List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);

        System.out.println("Original: " + numbers);

        // Ascending (natural order)
        numbers.sort(Comparator.naturalOrder());
        System.out.println("Ascending: " + numbers);

        // Descending
        numbers.sort(Comparator.reverseOrder());
        System.out.println("Descending: " + numbers);
        System.out.println("\nReversed on custom:");

        List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");

        System.out.println("Original: " + words);

        // By length ascending
        words.sort(Comparator.comparing(String::length));
        System.out.println("By length asc: " + words);

        // By length descending
        words.sort(Comparator.comparing(String::length).reversed());
        System.out.println("By length desc: " + words);
        System.out.println("\nReverse natural order:");

        List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");

        System.out.println("Original: " + names);

        // Natural order (alphabetic)
        names.sort(Comparator.naturalOrder());
        System.out.println("Alphabetic: " + names);

        // Reverse alphabetic
        names.sort(Comparator.reverseOrder());
        System.out.println("Reverse alpha: " + names);

        // Using reversed() - need explicit type parameter for type inference
        names.sort(Comparator.<String>naturalOrder().reversed());
        System.out.println("Using reversed(): " + names);
        System.out.println("\nCustom objects:");

        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        );

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

        // By age ascending
        people.sort(Comparator.comparing(Person::getAge));
        System.out.println("\nAge ascending:");
        people.forEach(System.out::println);

        // By age descending
        people.sort(Comparator.comparing(Person::getAge).reversed());
        System.out.println("\nAge descending:");
        people.forEach(System.out::println);
        System.out.println("\nComparable reversed:");

        List<Product> products = Arrays.asList(
            new Product("Widget", 29.99),
            new Product("Gadget", 49.99),
            new Product("Tool", 19.99)
        );

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

        // Natural order (by price, defined in Product)
        Collections.sort(products);
        System.out.println("\nNatural order:");
        products.forEach(System.out::println);

        // Reverse natural order
        Collections.sort(products, Comparator.reverseOrder());
        System.out.println("\nReverse natural:");
        products.forEach(System.out::println);
        System.out.println("\nChained with reversed:");

        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "Engineering", 75000),
            new Employee("Bob", "Sales", 60000),
            new Employee("Charlie", "Engineering", 80000)
        );

        // Dept ascending, salary descending
        employees.sort(
            Comparator.comparing(Employee::getDepartment)
                      .thenComparing(
                          Comparator.comparingDouble(Employee::getSalary).reversed()
                      )
        );

        System.out.println("Dept asc, salary desc:");
        employees.forEach(System.out::println);
        System.out.println("\nReversing entire chain:");

        List<Student> students = Arrays.asList(
            new Student("Alice", "A", 85),
            new Student("Bob", "B", 92),
            new Student("Charlie", "A", 95)
        );

        // Normal: grade, then score
        students.sort(
            Comparator.comparing(Student::getGrade)
                      .thenComparingInt(Student::getScore)
        );

        System.out.println("Normal order:");
        students.forEach(System.out::println);

        // Reversed: descending grade and score
        students.sort(
            Comparator.comparing(Student::getGrade)
                      .thenComparingInt(Student::getScore)
                      .reversed()
        );

        System.out.println("\nReversed order:");
        students.forEach(System.out::println);
        System.out.println("\nCollections methods:");

        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);

        System.out.println("Original: " + nums);

        // Collections.reverse (in-place)
        Collections.reverse(nums);
        System.out.println("After reverse(): " + nums);

        // Sort ascending
        Collections.sort(nums);
        System.out.println("After sort(): " + nums);

        // Sort descending
        Collections.sort(nums, Comparator.reverseOrder());
        System.out.println("Reverse order sort: " + nums);
        System.out.println("\nMin/Max with reverse:");

        List<Double> prices = Arrays.asList(19.99, 29.99, 9.99, 39.99);

        // Min with natural order
        double min = Collections.min(prices);
        System.out.println("Min: $" + min);

        // Max with natural order
        double max = Collections.max(prices);
        System.out.println("Max: $" + max);

        // "Min" with reverse order (actually max)
        double reverseMin = Collections.min(prices, Comparator.reverseOrder());
        System.out.println("Min with reverseOrder: $" + reverseMin);
    }

    static class Person {
        private String name;
        private int age;

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

        int getAge() { return age; }

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

    static class Product implements Comparable<Product> {
        private String name;
        private double price;

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

        @Override
        public int compareTo(Product other) {
            return Double.compare(this.price, other.price);
        }

        @Override
        public String toString() {
            return String.format("%s: $%.2f", name, price);
        }
    }

    static class Employee {
        private String name;
        private String department;
        private double salary;

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

        String getDepartment() { return department; }
        double getSalary() { return salary; }

        @Override
        public String toString() {
            return String.format("%s (%s, $%.0f)", name, department, salary);
        }
    }

    static class Student {
        private String name;
        private String grade;
        private int score;

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

        String getGrade() { return grade; }
        int getScore() { return score; }

        @Override
        public String toString() {
            return String.format("%s: %s (%d)", name, grade, score);
        }
    }
}

Null-Safe Comparators

Handle null values in comparisons gracefully.

Nullsafe.java
// Null-safe comparators

import java.util.*;

public class Nullsafe {
    public static void main(String[] args) {
        System.out.println("NullsFirst:");

        List<String> names = Arrays.asList("Alice", null, "Charlie", "Bob", null);

        System.out.println("Original: " + names);

        // Nulls first, then natural order
        names.sort(Comparator.nullsFirst(String::compareTo));
        System.out.println("Nulls first: " + names);
        System.out.println("\nNullsLast:");

        List<String> words = Arrays.asList("apple", null, "banana", null, "cherry");

        System.out.println("Original: " + words);

        // Nulls last, then natural order
        words.sort(Comparator.nullsLast(String::compareTo));
        System.out.println("Nulls last: " + words);
        System.out.println("\nCustom comparator with nulls:");

        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person(null, 25),
            new Person("Charlie", 35),
            new Person("Bob", null)
        );

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

        // Sort by name (nulls last)
        people.sort(
            Comparator.comparing(
                Person::getName,
                Comparator.nullsLast(String::compareTo)
            )
        );

        System.out.println("\nName (nulls last):");
        people.forEach(System.out::println);
        System.out.println("\nMultiple null fields:");

        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "Engineering", null),
            new Employee(null, "Sales", 60000.0),
            new Employee("Charlie", null, 80000.0),
            new Employee("Bob", "Engineering", 70000.0)
        );

        // Sort by dept (nulls last), then name (nulls first)
        employees.sort(
            Comparator.comparing(
                Employee::getDepartment,
                Comparator.nullsLast(String::compareTo)
            )
            .thenComparing(
                Employee::getName,
                Comparator.nullsFirst(String::compareTo)
            )
        );

        System.out.println("Dept (nulls last), name (nulls first):");
        employees.forEach(System.out::println);
        System.out.println("\nNull-safe chaining:");

        List<Product> products = Arrays.asList(
            new Product("Widget", null, 100),
            new Product("Gadget", "A", null),
            new Product(null, "B", 50),
            new Product("Tool", "A", 200)
        );

        // Category nulls last, then quantity nulls first
        products.sort(
            Comparator.comparing(
                Product::getCategory,
                Comparator.nullsLast(String::compareTo)
            )
            .thenComparing(
                Product::getQuantity,
                Comparator.nullsFirst(Integer::compareTo)
            )
        );

        System.out.println("Category/quantity null-safe:");
        products.forEach(System.out::println);
        System.out.println("\nAll nulls handling:");

        List<Integer> numbers = Arrays.asList(5, null, 2, null, 8, 1, null);

        System.out.println("Original: " + numbers);

        // Nulls first
        numbers.sort(Comparator.nullsFirst(Integer::compareTo));
        System.out.println("Nulls first: " + numbers);

        // Nulls last
        numbers.sort(Comparator.nullsLast(Integer::compareTo));
        System.out.println("Nulls last: " + numbers);
        System.out.println("\nNatural order with nulls:");

        List<Double> values = Arrays.asList(3.14, null, 2.71, 1.41, null);

        System.out.println("Original: " + values);

        // Natural order, nulls first
        values.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
        System.out.println("Natural, nulls first: " + values);
        System.out.println("\nReversed with nulls:");

        List<String> items = Arrays.asList("Zebra", null, "Apple", "Mango", null);

        System.out.println("Original: " + items);

        // Nulls first, descending
        items.sort(
            Comparator.nullsFirst(Comparator.<String>naturalOrder().reversed())
        );
        System.out.println("Nulls first, desc: " + items);

        // Nulls last, descending
        items.sort(
            Comparator.nullsLast(Comparator.<String>naturalOrder().reversed())
        );
        System.out.println("Nulls last, desc: " + items);
        System.out.println("\nCustom null object handling:");

        List<Task> tasks = Arrays.asList(
            new Task("Fix bug", 1),
            null,
            new Task("Write docs", 3),
            null,
            new Task("Review", 2)
        );

        System.out.println("Original:");
        tasks.forEach(t -> System.out.println("  " + t));

        // Handle entire null objects
        tasks.sort(Comparator.nullsLast(
            Comparator.comparing(Task::getPriority)
        ));

        System.out.println("\nNull objects last:");
        tasks.forEach(t -> System.out.println("  " + t));
    }

    static class Person {
        private String name;
        private Integer age;

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

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

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

    static class Employee {
        private String name;
        private String department;
        private Double salary;

        Employee(String name, String department, Double salary) {
            this.name = name;
            this.department = department;
            this.salary = salary;
        }

        String getName() { return name; }
        String getDepartment() { return department; }
        Double getSalary() { return salary; }

        @Override
        public String toString() {
            return String.format("%s (%s, $%.0f)",
                name, department, salary == null ? 0.0 : salary);
        }
    }

    static class Product {
        private String name;
        private String category;
        private Integer quantity;

        Product(String name, String category, Integer quantity) {
            this.name = name;
            this.category = category;
            this.quantity = quantity;
        }

        String getCategory() { return category; }
        Integer getQuantity() { return quantity; }

        @Override
        public String toString() {
            return String.format("%s [%s, qty=%d]",
                name, category, quantity == null ? 0 : quantity);
        }
    }

    static class Task {
        private String title;
        private int priority;

        Task(String title, int priority) {
            this.title = title;
            this.priority = priority;
        }

        int getPriority() { return priority; }

        @Override
        public String toString() {
            return String.format("%s (priority=%d)", title, priority);
        }
    }
}

@seealso collections_util

Null handling Use Comparator.nullsFirst() or nullsLast() to specify where nulls should appear in sorted order.

Exercise: Practical.java

Sort employees by department then salary, handling null departments