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.

Comparator A functional interface defining comparison logic externally, allowing multiple sort orders for the same class.

Basic Comparator

Create simple comparators using lambdas.

extraValue
Basic.java
Replay: real traced execution (multi-file project)
// 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 = 3;
        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 = 0;
        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 = 10;
        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 + ")";
        }
    }
}
  1. words ← [apple, pie, banana, kiwi], byLength ← ⟨Basic$1 A⟩

    5public class Basic {6    public static void main(String[] args) {7        System.out.println("String length comparator:");89        List<String> words→ [apple, pie, banana, kiwi] = Arrays.asList("apple", "pie", "banana", "kiwi");1011        System.out.println("Original: " + words[apple, pie, banana, kiwi]);1213        // Sort by length14        Comparator<String> byLength→ ⟨Basic$1 A⟩ = new Comparator<String>() {15            public int compare(String a, String b) {16                return a.length() - b.length();17            }18        };1920        words.sort(byLength⟨Basic$1 A⟩);21        System.out.println("By length: " + words);
    outputString length comparator:
    Original: [apple, pie, banana, kiwi]
  2. public int compare(String a, String b)

    pass 1 of 5
    14Comparator<String> byLength = new Comparator<String>() {15    public int compare(String apie, String bapple) {16        return a.length() - b.length();17    }
    All 5 passes — pass 1 is the card above
    passab
    1pieapple
    2bananapie
    3bananaapple
    4kiwiapple
    5kiwipie
  3. fruits ← [strawberry, fig, apple, pear], names ← [Alice, bob, Charlie, david]

    20words.sort(byLength⟨Basic$1 A⟩);21System.out.println("By length: " + words[pie, kiwi, apple, banana]);22System.out.println("\nLambda syntax:");2324List<String> fruits→ [strawberry, fig, apple, pear] = Arrays.asList("strawberry", "fig", "apple", "pear");2526System.out.println("Original: " + fruits[strawberry, fig, apple, pear]);2728// Lambda comparator29fruits.sort((a, b) -> a.length() - b.length());30System.out.println("By length: " + fruits[fig, pear, apple, strawberry]);3132// Reverse length33fruits.sort((a, b) -> b.length() - a.length());34System.out.println("Reverse length: " + fruits[strawberry, apple, pear, fig]);35System.out.println("\nCase-insensitive:");3637List<String> names→ [Alice, bob, Charlie, david] = Arrays.asList("Alice", "bob", "Charlie", "david");3839System.out.println("Original: " + names[Alice, bob, Charlie, david]);4041names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));42System.out.println("Case-insensitive: " + names[Alice, bob, Charlie, david]);43System.out.println("\nNumbers:");4445List<Integer> numbers→ [5, 2, 8, 1, 9] = Arrays.asList(5, 2, 8, 1, 9);4647System.out.println("Original: " + numbers[5, 2, 8, 1, 9]);4849// Ascending50numbers.sort((a, b) -> a - b);51System.out.println("Ascending: " + numbers[1, 2, 5, 8, 9]);5253// Descending54numbers.sort((a, b) -> b - a);55System.out.println("Descending: " + numbers[9, 8, 5, 2, 1]);56System.out.println("\nCustom objects:");5758List<Person> people = Arrays.asList(59    new Person("Alice", 30),60    new Person("Bob", 25),61    new Person("Charlie", 35)62);
    outputBy length: [pie, kiwi, apple, banana]
    
    Lambda syntax:
    Original: [strawberry, fig, apple, pear]
    By length: [fig, pear, apple, strawberry]
    Reverse length: [strawberry, apple, pear, fig]
    
    Case-insensitive:
    Original: [Alice, bob, Charlie, david]
    Case-insensitive: [Alice, bob, Charlie, david]
    
    Numbers:
    Original: [5, 2, 8, 1, 9]
    Ascending: [1, 2, 5, 8, 9]
    Descending: [9, 8, 5, 2, 1]
    
    Custom objects:
  4. this.name ← Alice, this.age ← 30

    pass 1 of 6
    122Person(String nameAlice, int age30) {123    this.name→ Alice = nameAlice;124    this.age→ 30 = age30;125}
    All 6 passes — pass 1 is the card above
    passnameagethis.namethis.agepeoplebyAgebyNamestaffitemsextraValuevaluesminmax
    1Alice30Alice30
    2Bob25Bob25
    3Charlie35Charlie35[Alice (30), Bob (25), Charlie (35)]⟨Basic lambda B⟩⟨Basic lambda C⟩
    4David28David28
    5Alice32Alice32
    6Bob28Bob28⟨Basic lambda B⟩⟨Basic lambda C⟩[David (28), Alice (32), Bob (28)][Zebra, Apple, Mango, Banana]3[5, 2, 8, 1, 9, 3]19
  1. words ← [apple, pie, banana, kiwi], byLength ← ⟨Basic$1 A⟩

    5public class Basic {6    public static void main(String[] args) {7        System.out.println("String length comparator:");89        List<String> words→ [apple, pie, banana, kiwi] = Arrays.asList("apple", "pie", "banana", "kiwi");1011        System.out.println("Original: " + words[apple, pie, banana, kiwi]);1213        // Sort by length14        Comparator<String> byLength→ ⟨Basic$1 A⟩ = new Comparator<String>() {15            public int compare(String a, String b) {16                return a.length() - b.length();17            }18        };1920        words.sort(byLength⟨Basic$1 A⟩);21        System.out.println("By length: " + words);
    outputString length comparator:
    Original: [apple, pie, banana, kiwi]
  2. public int compare(String a, String b)

    pass 1 of 5
    14Comparator<String> byLength = new Comparator<String>() {15    public int compare(String apie, String bapple) {16        return a.length() - b.length();17    }
    All 5 passes — pass 1 is the card above
    passab
    1pieapple
    2bananapie
    3bananaapple
    4kiwiapple
    5kiwipie
  3. fruits ← [strawberry, fig, apple, pear], names ← [Alice, bob, Charlie, david]

    20words.sort(byLength⟨Basic$1 A⟩);21System.out.println("By length: " + words[pie, kiwi, apple, banana]);22System.out.println("\nLambda syntax:");2324List<String> fruits→ [strawberry, fig, apple, pear] = Arrays.asList("strawberry", "fig", "apple", "pear");2526System.out.println("Original: " + fruits[strawberry, fig, apple, pear]);2728// Lambda comparator29fruits.sort((a, b) -> a.length() - b.length());30System.out.println("By length: " + fruits[fig, pear, apple, strawberry]);3132// Reverse length33fruits.sort((a, b) -> b.length() - a.length());34System.out.println("Reverse length: " + fruits[strawberry, apple, pear, fig]);35System.out.println("\nCase-insensitive:");3637List<String> names→ [Alice, bob, Charlie, david] = Arrays.asList("Alice", "bob", "Charlie", "david");3839System.out.println("Original: " + names[Alice, bob, Charlie, david]);4041names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));42System.out.println("Case-insensitive: " + names[Alice, bob, Charlie, david]);43System.out.println("\nNumbers:");4445List<Integer> numbers→ [5, 2, 8, 1, 9] = Arrays.asList(5, 2, 8, 1, 9);4647System.out.println("Original: " + numbers[5, 2, 8, 1, 9]);4849// Ascending50numbers.sort((a, b) -> a - b);51System.out.println("Ascending: " + numbers[1, 2, 5, 8, 9]);5253// Descending54numbers.sort((a, b) -> b - a);55System.out.println("Descending: " + numbers[9, 8, 5, 2, 1]);56System.out.println("\nCustom objects:");5758List<Person> people = Arrays.asList(59    new Person("Alice", 30),60    new Person("Bob", 25),61    new Person("Charlie", 35)62);
    outputBy length: [pie, kiwi, apple, banana]
    
    Lambda syntax:
    Original: [strawberry, fig, apple, pear]
    By length: [fig, pear, apple, strawberry]
    Reverse length: [strawberry, apple, pear, fig]
    
    Case-insensitive:
    Original: [Alice, bob, Charlie, david]
    Case-insensitive: [Alice, bob, Charlie, david]
    
    Numbers:
    Original: [5, 2, 8, 1, 9]
    Ascending: [1, 2, 5, 8, 9]
    Descending: [9, 8, 5, 2, 1]
    
    Custom objects:
  4. this.name ← Alice, this.age ← 30

    pass 1 of 6
    122Person(String nameAlice, int age30) {123    this.name→ Alice = nameAlice;124    this.age→ 30 = age30;125}
    All 6 passes — pass 1 is the card above
    passnameagethis.namethis.agepeoplebyAgebyNamestaffitemsextraValuevaluesminmax
    1Alice30Alice30
    2Bob25Bob25
    3Charlie35Charlie35[Alice (30), Bob (25), Charlie (35)]⟨Basic lambda B⟩⟨Basic lambda C⟩
    4David28David28
    5Alice32Alice32
    6Bob28Bob28⟨Basic lambda B⟩⟨Basic lambda C⟩[David (28), Alice (32), Bob (28)][Zebra, Apple, Mango, Banana]0[5, 2, 8, 1, 9, 0]09
  1. words ← [apple, pie, banana, kiwi], byLength ← ⟨Basic$1 A⟩

    5public class Basic {6    public static void main(String[] args) {7        System.out.println("String length comparator:");89        List<String> words→ [apple, pie, banana, kiwi] = Arrays.asList("apple", "pie", "banana", "kiwi");1011        System.out.println("Original: " + words[apple, pie, banana, kiwi]);1213        // Sort by length14        Comparator<String> byLength→ ⟨Basic$1 A⟩ = new Comparator<String>() {15            public int compare(String a, String b) {16                return a.length() - b.length();17            }18        };1920        words.sort(byLength⟨Basic$1 A⟩);21        System.out.println("By length: " + words);
    outputString length comparator:
    Original: [apple, pie, banana, kiwi]
  2. public int compare(String a, String b)

    pass 1 of 5
    14Comparator<String> byLength = new Comparator<String>() {15    public int compare(String apie, String bapple) {16        return a.length() - b.length();17    }
    All 5 passes — pass 1 is the card above
    passab
    1pieapple
    2bananapie
    3bananaapple
    4kiwiapple
    5kiwipie
  3. fruits ← [strawberry, fig, apple, pear], names ← [Alice, bob, Charlie, david]

    20words.sort(byLength⟨Basic$1 A⟩);21System.out.println("By length: " + words[pie, kiwi, apple, banana]);22System.out.println("\nLambda syntax:");2324List<String> fruits→ [strawberry, fig, apple, pear] = Arrays.asList("strawberry", "fig", "apple", "pear");2526System.out.println("Original: " + fruits[strawberry, fig, apple, pear]);2728// Lambda comparator29fruits.sort((a, b) -> a.length() - b.length());30System.out.println("By length: " + fruits[fig, pear, apple, strawberry]);3132// Reverse length33fruits.sort((a, b) -> b.length() - a.length());34System.out.println("Reverse length: " + fruits[strawberry, apple, pear, fig]);35System.out.println("\nCase-insensitive:");3637List<String> names→ [Alice, bob, Charlie, david] = Arrays.asList("Alice", "bob", "Charlie", "david");3839System.out.println("Original: " + names[Alice, bob, Charlie, david]);4041names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));42System.out.println("Case-insensitive: " + names[Alice, bob, Charlie, david]);43System.out.println("\nNumbers:");4445List<Integer> numbers→ [5, 2, 8, 1, 9] = Arrays.asList(5, 2, 8, 1, 9);4647System.out.println("Original: " + numbers[5, 2, 8, 1, 9]);4849// Ascending50numbers.sort((a, b) -> a - b);51System.out.println("Ascending: " + numbers[1, 2, 5, 8, 9]);5253// Descending54numbers.sort((a, b) -> b - a);55System.out.println("Descending: " + numbers[9, 8, 5, 2, 1]);56System.out.println("\nCustom objects:");5758List<Person> people = Arrays.asList(59    new Person("Alice", 30),60    new Person("Bob", 25),61    new Person("Charlie", 35)62);
    outputBy length: [pie, kiwi, apple, banana]
    
    Lambda syntax:
    Original: [strawberry, fig, apple, pear]
    By length: [fig, pear, apple, strawberry]
    Reverse length: [strawberry, apple, pear, fig]
    
    Case-insensitive:
    Original: [Alice, bob, Charlie, david]
    Case-insensitive: [Alice, bob, Charlie, david]
    
    Numbers:
    Original: [5, 2, 8, 1, 9]
    Ascending: [1, 2, 5, 8, 9]
    Descending: [9, 8, 5, 2, 1]
    
    Custom objects:
  4. this.name ← Alice, this.age ← 30

    pass 1 of 6
    122Person(String nameAlice, int age30) {123    this.name→ Alice = nameAlice;124    this.age→ 30 = age30;125}
    All 6 passes — pass 1 is the card above
    passnameagethis.namethis.agepeoplebyAgebyNamestaffitemsextraValuevaluesminmax
    1Alice30Alice30
    2Bob25Bob25
    3Charlie35Charlie35[Alice (30), Bob (25), Charlie (35)]⟨Basic lambda B⟩⟨Basic lambda C⟩
    4David28David28
    5Alice32Alice32
    6Bob28Bob28⟨Basic lambda B⟩⟨Basic lambda C⟩[David (28), Alice (32), Bob (28)][Zebra, Apple, Mango, Banana]10[5, 2, 8, 1, 9, 10]110
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
Replay: real traced execution (multi-file project)
// 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);
        }
    }
}
  1. public static void main(String[] args)

    6public class Comparing {7    public static void main(String[] args) {8        System.out.println("Basic comparing:");910        List<Person> people = Arrays.asList(11            new Person("Alice", 30, 75000),12            new Person("Bob", 25, 60000),13            new Person("Charlie", 35, 80000)14        );
    outputBasic comparing:
  2. this.name ← Alice, this.age ← 30, this.salary ← 75000.0

    pass 1 of 15
    158Person(String nameAlice, int age30, double salary75000.0) {159    this.name→ Alice = nameAlice;160    this.age→ 30 = age30;161    this.salary→ 75000.0 = salary75000.0;162}
    15 passes — pass 1 is the card above
    passnameagesalarythis.namethis.agethis.salarypeoplestaffemployeesteam
    1Alice3075000.0Alice3075000.0
    2Bob2560000.0Bob2560000.0
    3Charlie3580000.0Charlie3580000.0[Alice (age=30, salary=$75000), Bob (age=25, salary=$60000), Charlie (age=35, salary=$80000)]
    4David2870000.0David2870000.0
    5Alice3275000.0Alice3275000.0
    6Bob2865000.0Bob2865000.0[David (age=28, salary=$70000), Alice (age=32, salary=$75000), Bob (age=28, salary=$65000)]
    7Alice3070000.0Alice3070000.0
    8Bob2570000.0Bob2570000.0
    9Charlie3065000.0Charlie3065000.0[Alice (age=30, salary=$70000), Bob (age=25, salary=$70000), Charlie (age=30, salary=$65000)]
    ⋯ 4 more passes ⋯
    14Alice3075000.0Alice3075000.0
    15Bob2560000.0Bob2560000.0
  3. int getAge()

    pass 1 of 16
    164String getName() { return name; }165int getAge() { return age25; }166double getSalary() { return salary; }
    16 passes — pass 1 is the card above
    passage
    125
    230
    335
    425
    535
    630
    725
    830
    925
    ⋯ 5 more passes ⋯
    1525
    1630
  4. people.sort(Comparator.comparing(Person::getAge));

    19// Sort by age using method reference20people.sort(Comparator.comparing(Person::getAge));21System.out.println("\nBy age:");22people.forEach(System.out::println);23System.out.println("\nDifferent keys:");2425List<Person> staff = Arrays.asList(26    new Person("David", 28, 70000),27    new Person("Alice", 32, 75000),28    new Person("Bob", 28, 65000)29);
    output
    By age:
    
    Different keys:
  5. String getName()

    pass 1 of 18
    164String getName() { return nameAlice; }165int getAge() { return age; }
    18 passes — pass 1 is the card above
    passname
    1Alice
    2David
    3Bob
    4Alice
    5Bob
    6David
    7Bob
    8Alice
    9Alice
    ⋯ 7 more passes ⋯
    17Bob
    18Alice
  6. staff.sort(Comparator.comparing(Person::getName));

    31// Sort by name32staff.sort(Comparator.comparing(Person::getName));33System.out.println("By name:");34staff.forEach(System.out::println);3536// Sort by salary37staff.sort(Comparator.comparing(Person::getSalary));38System.out.println("\nBy salary:");
    outputBy name:
  7. double getSalary()

    pass 1 of 12
    165int getAge() { return age; }166double getSalary() { return salary65000.0; }
    All 12 passes — pass 1 is the card above
    passsalary
    165000.0
    275000.0
    370000.0
    465000.0
    570000.0
    675000.0
    770000.0
    865000.0
    970000.0
    1070000.0
    1165000.0
    1270000.0
  8. words ← [apple, pie, banana, kiwi]

    36// Sort by salary37staff.sort(Comparator.comparing(Person::getSalary));38System.out.println("\nBy salary:");39staff.forEach(System.out::println);40System.out.println("\nLambda extractor:");4142List<String> words→ [apple, pie, banana, kiwi] = Arrays.asList("apple", "pie", "banana", "kiwi");4344// Sort by length (lambda)45words.sort(Comparator.comparing(s -> s.length()));46System.out.println("By length: " + words[pie, kiwi, apple, banana]);4748// Sort by last character49words.sort(Comparator.comparing(s -> s.charAt(s.length() - 1)));50System.out.println("By last char: " + words[banana, pie, apple, kiwi]);51System.out.println("\nComparingInt/Long/Double:");5253List<Product> products = Arrays.asList(54    new Product("Widget", 29.99, 100),55    new Product("Gadget", 49.99, 50),56    new Product("Tool", 19.99, 200)57);
    output
    By salary:
    
    Lambda extractor:
    By length: [pie, kiwi, apple, banana]
    By last char: [banana, pie, apple, kiwi]
    
    ComparingInt/Long/Double:
  9. this.name ← Widget, this.price ← 29.99, this.quantity ← 100

    pass 1 of 6
    180Product(String nameWidget, double price29.99, int quantity100) {181    this.name→ Widget = nameWidget;182    this.price→ 29.99 = price29.99;183    this.quantity→ 100 = quantity100;184}
    All 6 passes — pass 1 is the card above
    passnamepricequantitythis.namethis.pricethis.quantityproductsitems
    1Widget29.99100Widget29.99100
    2Gadget49.9950Gadget49.9950
    3Tool19.99200Tool19.99200[Widget: $29.99 (qty=100), Gadget: $49.99 (qty=50), Tool: $19.99 (qty=200)]
    4A20.0100A20.0100
    5B30.050B30.050
    6C10.0200C10.0200[A: $20.00 (qty=100), B: $30.00 (qty=50), C: $10.00 (qty=200)]
  10. int getQuantity()

    pass 1 of 6
    186double getPrice() { return price; }187int getQuantity() { return quantity50; }
    All 6 passes — pass 1 is the card above
    passquantity
    150
    2100
    3200
    450
    5200
    6100
  11. products.sort(Comparator.comparingInt(Product::getQuantity));

    59// comparingInt for primitives (more efficient)60products.sort(Comparator.comparingInt(Product::getQuantity));61System.out.println("By quantity:");62products.forEach(System.out::println);6364// comparingDouble for prices65products.sort(Comparator.comparingDouble(Product::getPrice));66System.out.println("\nBy price:");
    outputBy quantity:
  12. double getPrice()

    pass 1 of 10
    186double getPrice() { return price29.99; }187int getQuantity() { return quantity; }
    All 10 passes — pass 1 is the card above
    passprice
    129.99
    249.99
    319.99
    429.99
    520.0
    630.0
    730.0
    810.0
    920.0
    1010.0
  13. products.sort(Comparator.comparingDouble(Product::getPrice));

    64// comparingDouble for prices65products.sort(Comparator.comparingDouble(Product::getPrice));66System.out.println("\nBy price:");67products.forEach(System.out::println);68System.out.println("\nChained comparators:");6970List<Person> employees = Arrays.asList(71    new Person("Alice", 30, 70000),72    new Person("Bob", 25, 70000),73    new Person("Charlie", 30, 65000)74);
    output
    By price:
    
    Chained comparators:
  14. employees.sort(

    76// Sort by salary, then by age77employees.sort(78    Comparator.comparing(Person::getSalary)79              .thenComparing(Person::getAge)80);8182System.out.println("By salary, then age:");83employees.forEach(System.out::println);84System.out.println("\nThenComparing variants:");8586List<Person> team = Arrays.asList(87    new Person("Alice", 30, 70000),88    new Person("Alice", 25, 60000),89    new Person("Bob", 30, 70000)90);
    outputBy salary, then age:
    
    ThenComparing variants:
  15. team.sort(

    92// By name, then age93team.sort(94    Comparator.comparing(Person::getName)95              .thenComparingInt(Person::getAge)96);9798System.out.println("By name, then age:");99team.forEach(System.out::println);100101// Three levels102team.sort(103    Comparator.comparing(Person::getName)104              .thenComparingInt(Person::getAge)105              .thenComparingDouble(Person::getSalary)106);
    outputBy name, then age:
  16. team.sort(

    101// Three levels102team.sort(103    Comparator.comparing(Person::getName)104              .thenComparingInt(Person::getAge)105              .thenComparingDouble(Person::getSalary)106);107108System.out.println("\nBy name, age, salary:");109team.forEach(System.out::println);110System.out.println("\nReverse order:");111112List<Product> items = Arrays.asList(113    new Product("A", 20.0, 100),114    new Product("B", 30.0, 50),115    new Product("C", 10.0, 200)116);
    output
    By name, age, salary:
    
    Reverse order:
  17. items.sort(Comparator.comparing(Product::getPrice).reversed());

    118// Descending price119items.sort(Comparator.comparing(Product::getPrice).reversed());120System.out.println("Price descending:");121items.forEach(System.out::println);122System.out.println("\nCustom extraction:");123124List<Employee> workers = Arrays.asList(125    new Employee("Alice", "Engineering", 30),126    new Employee("Bob", "Sales", 25),127    new Employee("Charlie", "Engineering", 35)128);
    outputPrice descending:
    
    Custom extraction:
  18. this.name ← Alice, this.department ← Engineering, this.age ← 30

    pass 1 of 3
    201Employee(String nameAlice, String departmentEngineering, int age30) {202    this.name→ Alice = nameAlice;203    this.department→ Engineering = departmentEngineering;204    this.age→ 30 = age30;205}
    All 3 passes — pass 1 is the card above
    passnamedepartmentagethis.namethis.departmentthis.ageworkers
    1AliceEngineering30AliceEngineering30
    2BobSales25BobSales25
    3CharlieEngineering35CharlieEngineering35[Alice (Engineering, 30), Bob (Sales, 25), Charlie (Engineering, 35)]
  19. String getName()

    pass 1 of 2
    207String getName() { return nameCharlie; }
  20. String getName()

    pass 2 of 2
    207String getName() { return nameAlice; }
  21. workers.sort(

    130// Sort by department length, then name131workers.sort(132    Comparator.comparing((Employee e) -> e.department.length())133              .thenComparing(Employee::getName)134);135136System.out.println("By dept length, then name:");137workers.forEach(System.out::println);138System.out.println("\nStream sorting:");139140List<Person> sorted = Arrays.asList(141    new Person("Charlie", 35, 80000),142    new Person("Alice", 30, 75000),143    new Person("Bob", 25, 60000)144)145.stream()146.sorted(Comparator.comparing(Person::getAge))147.toList();
    outputBy dept length, then name:
    
    Stream sorting:
  22. sorted ← [Bob (age=25, salary=$60000), Alice (age=30, salary=$75000), Charlie (age=35, salary=$80000)]

    140    List<Person> sorted→ [Bob (age=25, salary=$60000), Alice (age=30, salary=$75000), Charlie (age=35, salary=$80000)] = Arrays.asList(141        new Person("Charlie", 35, 80000),142        new Person("Alice", 30, 75000),143        new Person("Bob", 25, 60000)144    )145    .stream()146    .sorted(Comparator.comparing(Person::getAge))147    .toList();148149    System.out.println("Sorted stream:");150    sorted.forEach(System.out::println);151}
    outputSorted stream:
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
Replay: real traced execution (multi-file project)
// 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);
        }
    }
}
  1. public static void main(String[] args)

    5public class Chained {6    public static void main(String[] args) {7        System.out.println("Two-level sort:");89        List<Student> students = Arrays.asList(10            new Student("Alice", "A", 85),11            new Student("Bob", "B", 92),12            new Student("Charlie", "A", 78),13            new Student("David", "B", 85)14        );
    outputTwo-level sort:
  2. this.name ← Alice, this.grade ← A, this.score ← 85

    pass 1 of 7
    161Student(String nameAlice, String gradeA, int score85) {162    this.name→ Alice = nameAlice;163    this.grade→ A = gradeA;164    this.score→ 85 = score85;165}
    All 7 passes — pass 1 is the card above
    passnamegradescorethis.namethis.gradethis.scorestudentsroster
    1AliceA85AliceA85
    2BobB92BobB92
    3CharlieA78CharlieA78
    4DavidB85DavidB85[Alice: A (85), Bob: B (92), Charlie: A (78), David: B (85)]
    5AliceA85AliceA85
    6BobB92BobB92
    7CharlieA95CharlieA95[Alice: A (85), Bob: B (92), Charlie: A (95)]
  3. String getGrade()

    pass 1 of 20
    167String getGrade() { return gradeB; }168int getScore() { return score; }
    20 passes — pass 1 is the card above
    passgrade
    1B
    2A
    3A
    4B
    5A
    6B
    7A
    8A
    9B
    ⋯ 9 more passes ⋯
    19B
    20A
  4. int getScore()

    pass 1 of 6
    167String getGrade() { return grade; }168int getScore() { return score78; }
    All 6 passes — pass 1 is the card above
    passscore
    178
    285
    385
    492
    585
    695
  5. students.sort(

    19// Sort by grade, then score20students.sort(21    Comparator.comparing(Student::getGrade)22              .thenComparingInt(Student::getScore)23);2425System.out.println("\nBy grade, then score:");26students.forEach(System.out::println);27System.out.println("\nThree-level sort:");2829List<Employee> employees = Arrays.asList(30    new Employee("Alice", "Engineering", 30, 75000),31    new Employee("Bob", "Engineering", 30, 70000),32    new Employee("Charlie", "Sales", 25, 60000),33    new Employee("David", "Engineering", 25, 65000)34);
    output
    By grade, then score:
    
    Three-level sort:
  6. this.name ← Alice, this.department ← Engineering, this.age ← 30

    pass 1 of 4
    182Employee(String nameAlice, String departmentEngineering, int age30, double salary75000.0) {183    this.name→ Alice = nameAlice;184    this.department→ Engineering = departmentEngineering;185    this.age→ 30 = age30;186    this.salary→ 75000.0 = salary75000.0;187}
    All 4 passes — pass 1 is the card above
    passnamedepartmentagesalarythis.namethis.departmentthis.agethis.salaryemployees
    1AliceEngineering3075000.0AliceEngineering3075000.0
    2BobEngineering3070000.0BobEngineering3070000.0
    3CharlieSales2560000.0CharlieSales2560000.0
    4DavidEngineering2565000.0DavidEngineering2565000.0[Alice (Engineering, 30, $75000), Bob (Engineering, 30, $70000), Charlie (Sales, 25, $60000), David (Engineering, 25, $65000)]
  7. String getDepartment()

    pass 1 of 16
    189String getDepartment() { return departmentEngineering; }190int getAge() { return age; }
    16 passes — pass 1 is the card above
    passdepartment
    1Engineering
    2Engineering
    3Sales
    4Engineering
    5Sales
    6Engineering
    7Engineering
    8Engineering
    9Engineering
    ⋯ 5 more passes ⋯
    15Sales
    16Engineering
  8. int getAge()

    pass 1 of 10
    189String getDepartment() { return department; }190int getAge() { return age30; }191double getSalary() { return salary; }
    All 10 passes — pass 1 is the card above
    passage
    130
    230
    325
    430
    525
    630
    730
    825
    930
    1030
  9. double getSalary()

    pass 1 of 4
    190int getAge() { return age; }191double getSalary() { return salary70000.0; }
    All 4 passes — pass 1 is the card above
    passsalary
    170000.0
    275000.0
    375000.0
    470000.0
  10. employees.sort(

    36// Sort by department, age, then salary37employees.sort(38    Comparator.comparing(Employee::getDepartment)39              .thenComparingInt(Employee::getAge)40              .thenComparingDouble(Employee::getSalary)41);4243System.out.println("By dept, age, salary:");44employees.forEach(System.out::println);45System.out.println("\nMixed ascending/descending:");4647List<Product> products = Arrays.asList(48    new Product("Widget", "A", 29.99, 100),49    new Product("Gadget", "B", 49.99, 50),50    new Product("Tool", "A", 19.99, 200),51    new Product("Device", "B", 39.99, 150)52);
    outputBy dept, age, salary:
    
    Mixed ascending/descending:
  11. this.name ← Widget, this.category ← A, this.price ← 29.99, this.quantity ← 100

    pass 1 of 4
    206Product(String nameWidget, String categoryA, double price29.99, int quantity100) {207    this.name→ Widget = nameWidget;208    this.category→ A = categoryA;209    this.price→ 29.99 = price29.99;210    this.quantity→ 100 = quantity100;211}
    All 4 passes — pass 1 is the card above
    passnamecategorypricequantitythis.namethis.categorythis.pricethis.quantityproducts
    1WidgetA29.99100WidgetA29.99100
    2GadgetB49.9950GadgetB49.9950
    3ToolA19.99200ToolA19.99200
    4DeviceB39.99150DeviceB39.99150[Widget [A]: $29.99, Gadget [B]: $49.99, Tool [A]: $19.99, Device [B]: $39.99]
  12. String getCategory()

    pass 1 of 12
    213String getCategory() { return categoryB; }214double getPrice() { return price; }
    All 12 passes — pass 1 is the card above
    passcategory
    1B
    2A
    3A
    4B
    5A
    6B
    7A
    8A
    9B
    10A
    11B
    12B
  13. double getPrice()

    pass 1 of 4
    213String getCategory() { return category; }214double getPrice() { return price29.99; }
    All 4 passes — pass 1 is the card above
    passprice
    129.99
    219.99
    349.99
    439.99
  14. products.sort(

    54// Category ascending, price descending55products.sort(56    Comparator.comparing(Product::getCategory)57              .thenComparing(58                  Comparator.comparingDouble(Product::getPrice).reversed()59              )60);6162System.out.println("Category asc, price desc:");63products.forEach(System.out::println);64System.out.println("\nReverse entire chain:");6566List<Student> roster = Arrays.asList(67    new Student("Alice", "A", 85),68    new Student("Bob", "B", 92),69    new Student("Charlie", "A", 95)70);
    outputCategory asc, price desc:
    
    Reverse entire chain:
  15. priorityOrder ← {Medium=2, Low=3, High=1}

    72// Reverse the entire sort73roster.sort(74    Comparator.comparing(Student::getGrade)75              .thenComparingInt(Student::getScore)76              .reversed()77);7879System.out.println("Reversed grade/score:");80roster.forEach(System.out::println);81System.out.println("\nCustom order:");8283Map<String, Integer> priorityOrder→ {Medium=2, Low=3, High=1} = Map.of(84    "High", 1,85    "Medium", 2,86    "Low", 387);8889List<Task> tasks = Arrays.asList(90    new Task("Fix bug", "High", 2),91    new Task("Write docs", "Low", 1),92    new Task("Review code", "Medium", 3),93    new Task("Testing", "High", 1)94);
    outputReversed grade/score:
    
    Custom order:
  16. this.title ← Fix bug, this.priority ← High, this.days ← 2

    pass 1 of 4
    227Task(String titleFix bug, String priorityHigh, int days2) {228    this.title→ Fix bug = titleFix bug;229    this.priority→ High = priorityHigh;230    this.days→ 2 = days2;231}
    All 4 passes — pass 1 is the card above
    passtitleprioritydaysthis.titlethis.prioritythis.daystasks
    1Fix bugHigh2Fix bugHigh2
    2Write docsLow1Write docsLow1
    3Review codeMedium3Review codeMedium3
    4TestingHigh1TestingHigh1[Fix bug [High, 2d], Write docs [Low, 1d], Review code [Medium, 3d], Testing [High, 1d]]
  17. String getPriority()

    pass 1 of 12
    233String getPriority() { return priorityLow; }234int getDays() { return days; }
    All 12 passes — pass 1 is the card above
    passprioritydays
    1Low
    2High
    3Medium
    4Low
    5Medium
    6Low
    7Medium
    8High
    9High
    10Medium
    11High
    12High1
  18. int getDays()

    pass 1 of 2
    233String getPriority() { return priority; }234int getDays() { return days1; }
  19. int getDays()

    pass 2 of 2
    233String getPriority() { return priority; }234int getDays() { return days2; }
  20. tasks.sort(

    96// Sort by priority order, then days97tasks.sort(98    Comparator.comparing((Task t) -> priorityOrder.get(t.getPriority()))99              .thenComparingInt(Task::getDays)100);101102System.out.println("By priority, then days:");103tasks.forEach(System.out::println);104System.out.println("\nNull handling:");105106List<Person> people = Arrays.asList(107    new Person("Alice", "Engineering"),108    new Person("Bob", null),109    new Person("Charlie", "Sales"),110    new Person("David", null)111);
    outputBy priority, then days:
    
    Null handling:
  21. this.name ← Alice, this.department ← Engineering

    pass 1 of 4
    246Person(String nameAlice, String departmentEngineering) {247    this.name→ Alice = nameAlice;248    this.department→ Engineering = departmentEngineering;249}
    All 4 passes — pass 1 is the card above
    passnamedepartmentthis.namethis.departmentpeople
    1AliceEngineeringAliceEngineering
    2BobnullBobnull
    3CharlieSalesCharlieSales
    4DavidnullDavidnull[Alice (Engineering), Bob (null), Charlie (Sales), David (null)]
  22. String getDepartment()

    pass 1 of 12
    251String getName() { return name; }252String getDepartment() { return departmentnull; }
    All 12 passes — pass 1 is the card above
    passdepartmentname
    1null
    2Engineering
    3Sales
    4null
    5Sales
    6null
    7Sales
    8Engineering
    9null
    10Sales
    11null
    12nullDavid
  23. String getName()

    pass 1 of 2
    251String getName() { return nameDavid; }252String getDepartment() { return department; }
  24. String getName()

    pass 2 of 2
    251String getName() { return nameBob; }252String getDepartment() { return department; }
  25. people.sort(

    113// Nulls last, then alphabetic114people.sort(115    Comparator.comparing(116        Person::getDepartment,117        Comparator.nullsLast(String::compareTo)118    )119    .thenComparing(Person::getName)120);121122System.out.println("Dept (nulls last), then name:");123people.forEach(System.out::println);124System.out.println("\nStable sort:");125126List<Record> records = Arrays.asList(127    new Record(1, "A", 100),128    new Record(2, "B", 100),129    new Record(3, "A", 200),130    new Record(4, "B", 100)131);
    outputDept (nulls last), then name:
    
    Stable sort:
  26. this.id ← 1, this.category ← A, this.value ← 100

    pass 1 of 4
    265Record(int id1, String categoryA, int value100) {266    this.id→ 1 = id1;267    this.category→ A = categoryA;268    this.value→ 100 = value100;269}
    All 4 passes — pass 1 is the card above
    passidcategoryvaluethis.idthis.categorythis.valuerecords
    11A1001A100
    22B1002B100
    33A2003A200
    44B1004B100[#1: A=100, #2: B=100, #3: A=200, #4: B=100]
  27. String getCategory()

    pass 1 of 12
    271String getCategory() { return categoryB; }
    All 12 passes — pass 1 is the card above
    passcategory
    1B
    2A
    3A
    4B
    5A
    6B
    7A
    8A
    9B
    10A
    11B
    12B
  28. byDept ← ⟨Comparator lambda A⟩, byAge ← ⟨Comparator lambda B⟩

    136// Sort by category only - original order preserved within category137records.sort(Comparator.comparing(Record::getCategory));138139System.out.println("\nBy category (stable):");140records.forEach(System.out::println);141System.out.println("\nPerformance optimization:");142143// Reuse comparators144Comparator<Employee> byDept→ ⟨Comparator lambda A⟩ = Comparator.comparing(Employee::getDepartment);145Comparator<Employee> byAge→ ⟨Comparator lambda B⟩ = Comparator.comparingInt(Employee::getAge);146Comparator<Employee> bySalary→ ⟨Comparator lambda C⟩ = Comparator.comparingDouble(Employee::getSalary);147148Comparator<Employee> combined→ ⟨Comparator lambda D⟩ = byDept.thenComparing(byAge⟨Comparator lambda B⟩).thenComparing(bySalary⟨Comparator lambda C⟩);149150employees.sort(combined⟨Comparator lambda D⟩);
    output
    By category (stable):
    
    Performance optimization:
  29. employees.sort(combined);

    150    employees.sort(combined⟨Comparator lambda D⟩);151152    System.out.println("Using reusable comparators:");153    employees.forEach(System.out::println);154}
    outputUsing reusable comparators:
Secondary sort Use thenComparing() to break ties, like sorting by last name then first name.

Reverse Ordering

Invert sort order easily.

Reverse.java
Replay: real traced execution (multi-file project)
// 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);
        }
    }
}
  1. numbers ← [5, 2, 8, 1, 9, 3], words ← [apple, pie, banana, kiwi]

    5public class Reverse {6    public static void main(String[] args) {7        System.out.println("Basic reversed:");89        List<Integer> numbers→ [5, 2, 8, 1, 9, 3] = Arrays.asList(5, 2, 8, 1, 9, 3);1011        System.out.println("Original: " + numbers[5, 2, 8, 1, 9, 3]);1213        // Ascending (natural order)14        numbers.sort(Comparator.naturalOrder());15        System.out.println("Ascending: " + numbers[1, 2, 3, 5, 8, 9]);1617        // Descending18        numbers.sort(Comparator.reverseOrder());19        System.out.println("Descending: " + numbers[9, 8, 5, 3, 2, 1]);20        System.out.println("\nReversed on custom:");2122        List<String> words→ [apple, pie, banana, kiwi] = Arrays.asList("apple", "pie", "banana", "kiwi");2324        System.out.println("Original: " + words[apple, pie, banana, kiwi]);2526        // By length ascending27        words.sort(Comparator.comparing(String::length));28        System.out.println("By length asc: " + words[pie, kiwi, apple, banana]);2930        // By length descending31        words.sort(Comparator.comparing(String::length).reversed());32        System.out.println("By length desc: " + words[banana, apple, kiwi, pie]);33        System.out.println("\nReverse natural order:");3435        List<String> names→ [Charlie, Alice, Bob, David] = Arrays.asList("Charlie", "Alice", "Bob", "David");3637        System.out.println("Original: " + names[Charlie, Alice, Bob, David]);3839        // Natural order (alphabetic)40        names.sort(Comparator.naturalOrder());41        System.out.println("Alphabetic: " + names[Alice, Bob, Charlie, David]);4243        // Reverse alphabetic44        names.sort(Comparator.reverseOrder());45        System.out.println("Reverse alpha: " + names[David, Charlie, Bob, Alice]);4647        // Using reversed() - need explicit type parameter for type inference48        names.sort(Comparator.<String>naturalOrder().reversed());49        System.out.println("Using reversed(): " + names[David, Charlie, Bob, Alice]);50        System.out.println("\nCustom objects:");5152        List<Person> people = Arrays.asList(53            new Person("Alice", 30),54            new Person("Bob", 25),55            new Person("Charlie", 35)56        );
    outputBasic reversed:
    Original: [5, 2, 8, 1, 9, 3]
    Ascending: [1, 2, 3, 5, 8, 9]
    Descending: [9, 8, 5, 3, 2, 1]
    
    Reversed on custom:
    Original: [apple, pie, banana, kiwi]
    By length asc: [pie, kiwi, apple, banana]
    By length desc: [banana, apple, kiwi, pie]
    
    Reverse natural order:
    Original: [Charlie, Alice, Bob, David]
    Alphabetic: [Alice, Bob, Charlie, David]
    Reverse alpha: [David, Charlie, Bob, Alice]
    Using reversed(): [David, Charlie, Bob, Alice]
    
    Custom objects:
  2. this.name ← Alice, this.age ← 30

    pass 1 of 3
    172Person(String nameAlice, int age30) {173    this.name→ Alice = nameAlice;174    this.age→ 30 = age30;175}
    All 3 passes — pass 1 is the card above
    passnameagethis.namethis.agepeople
    1Alice30Alice30
    2Bob25Bob25
    3Charlie35Charlie35[Alice (30), Bob (25), Charlie (35)]
  3. int getAge()

    pass 1 of 10
    177int getAge() { return age25; }
    All 10 passes — pass 1 is the card above
    passage
    125
    230
    335
    425
    535
    630
    725
    830
    930
    1035
  4. people.sort(Comparator.comparing(Person::getAge));

    61// By age ascending62people.sort(Comparator.comparing(Person::getAge));63System.out.println("\nAge ascending:");64people.forEach(System.out::println);6566// By age descending67people.sort(Comparator.comparing(Person::getAge).reversed());68System.out.println("\nAge descending:");
    output
    Age ascending:
  5. people.sort(Comparator.comparing(Person::getAge).reversed());

    66// By age descending67people.sort(Comparator.comparing(Person::getAge).reversed());68System.out.println("\nAge descending:");69people.forEach(System.out::println);70System.out.println("\nComparable reversed:");7172List<Product> products = Arrays.asList(73    new Product("Widget", 29.99),74    new Product("Gadget", 49.99),75    new Product("Tool", 19.99)76);
    output
    Age descending:
    
    Comparable reversed:
  6. this.name ← Widget, this.price ← 29.99

    pass 1 of 3
    189Product(String nameWidget, double price29.99) {190    this.name→ Widget = nameWidget;191    this.price→ 29.99 = price29.99;192}
    All 3 passes — pass 1 is the card above
    passnamepricethis.namethis.priceproducts
    1Widget29.99Widget29.99
    2Gadget49.99Gadget49.99
    3Tool19.99Tool19.99[Widget: $29.99, Gadget: $49.99, Tool: $19.99]
  7. @Override public int compareTo(Product other)

    pass 1 of 6
    194@Override195public int compareTo(Product otherWidget: $29.99) {196    return Double.compare(this.price49.99, other.price29.99);197}
    All 6 passes — pass 1 is the card above
    passotherthis.priceother.price
    1Widget: $29.9949.9929.99
    2Gadget: $49.9919.9949.99
    3Gadget: $49.9919.9949.99
    4Widget: $29.9919.9929.99
    5Widget: $29.9919.9929.99
    6Gadget: $49.9929.9949.99
  8. products ← [Tool: $19.99, Widget: $29.99, Gadget: $49.99]

    81// Natural order (by price, defined in Product)82Collections.sort(products→ [Tool: $19.99, Widget: $29.99, Gadget: $49.99]);83System.out.println("\nNatural order:");84products.forEach(System.out::println);8586// Reverse natural order87Collections.sort(products[Tool: $19.99, Widget: $29.99, Gadget: $49.99], Comparator.reverseOrder());88System.out.println("\nReverse natural:");
    output
    Natural order:
  9. products ← [Gadget: $49.99, Widget: $29.99, Tool: $19.99]

    86// Reverse natural order87Collections.sort(products→ [Gadget: $49.99, Widget: $29.99, Tool: $19.99], Comparator.reverseOrder());88System.out.println("\nReverse natural:");89products.forEach(System.out::println);90System.out.println("\nChained with reversed:");9192List<Employee> employees = Arrays.asList(93    new Employee("Alice", "Engineering", 75000),94    new Employee("Bob", "Sales", 60000),95    new Employee("Charlie", "Engineering", 80000)96);
    output
    Reverse natural:
    
    Chained with reversed:
  10. this.name ← Alice, this.department ← Engineering, this.salary ← 75000.0

    pass 1 of 3
    210Employee(String nameAlice, String departmentEngineering, double salary75000.0) {211    this.name→ Alice = nameAlice;212    this.department→ Engineering = departmentEngineering;213    this.salary→ 75000.0 = salary75000.0;214}
    All 3 passes — pass 1 is the card above
    passnamedepartmentsalarythis.namethis.departmentthis.salaryemployees
    1AliceEngineering75000.0AliceEngineering75000.0
    2BobSales60000.0BobSales60000.0
    3CharlieEngineering80000.0CharlieEngineering80000.0[Alice (Engineering, $75000), Bob (Sales, $60000), Charlie (Engineering, $80000)]
  11. String getDepartment()

    pass 1 of 8
    216String getDepartment() { return departmentSales; }217double getSalary() { return salary; }
    All 8 passes — pass 1 is the card above
    passdepartmentsalary
    1Sales
    2Engineering
    3Engineering
    4Sales
    5Engineering
    6Sales
    7Engineering
    8Engineering75000.0
  12. double getSalary()

    pass 1 of 2
    216String getDepartment() { return department; }217double getSalary() { return salary75000.0; }
  13. double getSalary()

    pass 2 of 2
    216String getDepartment() { return department; }217double getSalary() { return salary80000.0; }
  14. employees.sort(

    98// Dept ascending, salary descending99employees.sort(100    Comparator.comparing(Employee::getDepartment)101              .thenComparing(102                  Comparator.comparingDouble(Employee::getSalary).reversed()103              )104);105106System.out.println("Dept asc, salary desc:");107employees.forEach(System.out::println);108System.out.println("\nReversing entire chain:");109110List<Student> students = Arrays.asList(111    new Student("Alice", "A", 85),112    new Student("Bob", "B", 92),113    new Student("Charlie", "A", 95)114);
    outputDept asc, salary desc:
    
    Reversing entire chain:
  15. this.name ← Alice, this.grade ← A, this.score ← 85

    pass 1 of 3
    230Student(String nameAlice, String gradeA, int score85) {231    this.name→ Alice = nameAlice;232    this.grade→ A = gradeA;233    this.score→ 85 = score85;234}
    All 3 passes — pass 1 is the card above
    passnamegradescorethis.namethis.gradethis.scorestudents
    1AliceA85AliceA85
    2BobB92BobB92
    3CharlieA95CharlieA95[Alice: A (85), Bob: B (92), Charlie: A (95)]
  16. String getGrade()

    pass 1 of 12
    236String getGrade() { return gradeB; }237int getScore() { return score; }
    All 12 passes — pass 1 is the card above
    passgrade
    1B
    2A
    3A
    4B
    5A
    6B
    7A
    8A
    9A
    10A
    11A
    12B
  17. int getScore()

    pass 1 of 4
    236String getGrade() { return grade; }237int getScore() { return score95; }
    All 4 passes — pass 1 is the card above
    passscore
    195
    285
    385
    495
  18. students.sort(

    116// Normal: grade, then score117students.sort(118    Comparator.comparing(Student::getGrade)119              .thenComparingInt(Student::getScore)120);121122System.out.println("Normal order:");123students.forEach(System.out::println);124125// Reversed: descending grade and score126students.sort(127    Comparator.comparing(Student::getGrade)128              .thenComparingInt(Student::getScore)129              .reversed()130);
    outputNormal order:
  19. nums ← [1, 2, 3, 4, 5], prices ← [19.99, 29.99, 9.99, 39.99], min ← 9.99

    125    // Reversed: descending grade and score126    students.sort(127        Comparator.comparing(Student::getGrade)128                  .thenComparingInt(Student::getScore)129                  .reversed()130    );131132    System.out.println("\nReversed order:");133    students.forEach(System.out::println);134    System.out.println("\nCollections methods:");135136    List<Integer> nums→ [1, 2, 3, 4, 5] = Arrays.asList(1, 2, 3, 4, 5);137138    System.out.println("Original: " + nums[1, 2, 3, 4, 5]);139140    // Collections.reverse (in-place)141    Collections.reverse(nums→ [5, 4, 3, 2, 1]);142    System.out.println("After reverse(): " + nums[5, 4, 3, 2, 1]);143144    // Sort ascending145    Collections.sort(nums→ [1, 2, 3, 4, 5]);146    System.out.println("After sort(): " + nums[1, 2, 3, 4, 5]);147148    // Sort descending149    Collections.sort(nums→ [5, 4, 3, 2, 1], Comparator.reverseOrder());150    System.out.println("Reverse order sort: " + nums[5, 4, 3, 2, 1]);151    System.out.println("\nMin/Max with reverse:");152153    List<Double> prices→ [19.99, 29.99, 9.99, 39.99] = Arrays.asList(19.99, 29.99, 9.99, 39.99);154155    // Min with natural order156    double min→ 9.99 = Collections.min(prices[19.99, 29.99, 9.99, 39.99]);157    System.out.println("Min: $" + min9.99);158159    // Max with natural order160    double max→ 39.99 = Collections.max(prices[19.99, 29.99, 9.99, 39.99]);161    System.out.println("Max: $" + max39.99);162163    // "Min" with reverse order (actually max)164    double reverseMin→ 39.99 = Collections.min(prices[19.99, 29.99, 9.99, 39.99], Comparator.reverseOrder());165    System.out.println("Min with reverseOrder: $" + reverseMin39.99);166}
    output
    Reversed order:
    
    Collections methods:
    Original: [1, 2, 3, 4, 5]
    After reverse(): [5, 4, 3, 2, 1]
    After sort(): [1, 2, 3, 4, 5]
    Reverse order sort: [5, 4, 3, 2, 1]
    
    Min/Max with reverse:
    Min: $9.99
    Max: $39.99
    Min with reverseOrder: $39.99

Null-Safe Comparators

Handle null values in comparisons gracefully.

Nullsafe.java
Replay: real traced execution (multi-file project)
// 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);
        }
    }
}
  1. names ← [Alice, null, Charlie, Bob, null], words ← [apple, null, banana, null, cherry]

    5public class Nullsafe {6    public static void main(String[] args) {7        System.out.println("NullsFirst:");89        List<String> names→ [Alice, null, Charlie, Bob, null] = Arrays.asList("Alice", null, "Charlie", "Bob", null);1011        System.out.println("Original: " + names[Alice, null, Charlie, Bob, null]);1213        // Nulls first, then natural order14        names.sort(Comparator.nullsFirst(String::compareTo));15        System.out.println("Nulls first: " + names[null, null, Alice, Bob, Charlie]);16        System.out.println("\nNullsLast:");1718        List<String> words→ [apple, null, banana, null, cherry] = Arrays.asList("apple", null, "banana", null, "cherry");1920        System.out.println("Original: " + words[apple, null, banana, null, cherry]);2122        // Nulls last, then natural order23        words.sort(Comparator.nullsLast(String::compareTo));24        System.out.println("Nulls last: " + words[apple, banana, cherry, null, null]);25        System.out.println("\nCustom comparator with nulls:");2627        List<Person> people = Arrays.asList(28            new Person("Alice", 30),29            new Person(null, 25),30            new Person("Charlie", 35),31            new Person("Bob", null)32        );
    outputNullsFirst:
    Original: [Alice, null, Charlie, Bob, null]
    Nulls first: [null, null, Alice, Bob, Charlie]
    
    NullsLast:
    Original: [apple, null, banana, null, cherry]
    Nulls last: [apple, banana, cherry, null, null]
    
    Custom comparator with nulls:
  2. this.name ← Alice, this.age ← 30

    pass 1 of 4
    158Person(String nameAlice, Integer age30) {159    this.name→ Alice = nameAlice;160    this.age→ 30 = age30;161}
    All 4 passes — pass 1 is the card above
    passnameagethis.namethis.agepeople
    1Alice30Alice30
    2null25null25
    3Charlie35Charlie35
    4BobnullBobnull[Alice (30), null (25), Charlie (35), Bob (null)]
  3. String getName()

    pass 1 of 12
    163String getName() { return namenull; }164Integer getAge() { return age; }
    All 12 passes — pass 1 is the card above
    passname
    1null
    2Alice
    3Charlie
    4null
    5Charlie
    6null
    7Charlie
    8Alice
    9Bob
    10Charlie
    11Bob
    12Alice
  4. people.sort(

    37// Sort by name (nulls last)38people.sort(39    Comparator.comparing(40        Person::getName,41        Comparator.nullsLast(String::compareTo)42    )43);4445System.out.println("\nName (nulls last):");46people.forEach(System.out::println);47System.out.println("\nMultiple null fields:");4849List<Employee> employees = Arrays.asList(50    new Employee("Alice", "Engineering", null),51    new Employee(null, "Sales", 60000.0),52    new Employee("Charlie", null, 80000.0),53    new Employee("Bob", "Engineering", 70000.0)54);
    output
    Name (nulls last):
    
    Multiple null fields:
  5. this.name ← Alice, this.department ← Engineering, this.salary ← null

    pass 1 of 4
    177Employee(String nameAlice, String departmentEngineering, Double salarynull) {178    this.name→ Alice = nameAlice;179    this.department→ Engineering = departmentEngineering;180    this.salary→ null = salarynull;181}
    All 4 passes — pass 1 is the card above
    passnamedepartmentsalarythis.namethis.departmentthis.salaryemployees
    1AliceEngineeringnullAliceEngineeringnull
    2nullSales60000.0nullSales60000.0
    3Charlienull80000.0Charlienull80000.0
    4BobEngineering70000.0BobEngineering70000.0[Alice (Engineering, $0), null (Sales, $60000), Charlie (null, $80000), Bob (Engineering, $70000)]
  6. String getDepartment()

    pass 1 of 10
    183String getName() { return name; }184String getDepartment() { return departmentSales; }185Double getSalary() { return salary; }
    All 10 passes — pass 1 is the card above
    passdepartmentname
    1Sales
    2Engineering
    3null
    4Sales
    5Engineering
    6null
    7Engineering
    8Sales
    9Engineering
    10EngineeringBob
  7. String getName()

    pass 1 of 2
    183String getName() { return nameBob; }184String getDepartment() { return department; }
  8. String getName()

    pass 2 of 2
    183String getName() { return nameAlice; }184String getDepartment() { return department; }
  9. employees.sort(

    56// Sort by dept (nulls last), then name (nulls first)57employees.sort(58    Comparator.comparing(59        Employee::getDepartment,60        Comparator.nullsLast(String::compareTo)61    )62    .thenComparing(63        Employee::getName,64        Comparator.nullsFirst(String::compareTo)65    )66);6768System.out.println("Dept (nulls last), name (nulls first):");69employees.forEach(System.out::println);70System.out.println("\nNull-safe chaining:");7172List<Product> products = Arrays.asList(73    new Product("Widget", null, 100),74    new Product("Gadget", "A", null),75    new Product(null, "B", 50),76    new Product("Tool", "A", 200)77);
    outputDept (nulls last), name (nulls first):
    
    Null-safe chaining:
  10. this.name ← Widget, this.category ← null, this.quantity ← 100

    pass 1 of 4
    199Product(String nameWidget, String categorynull, Integer quantity100) {200    this.name→ Widget = nameWidget;201    this.category→ null = categorynull;202    this.quantity→ 100 = quantity100;203}
    All 4 passes — pass 1 is the card above
    passnamecategoryquantitythis.namethis.categorythis.quantityproducts
    1Widgetnull100Widgetnull100
    2GadgetAnullGadgetAnull
    3nullB50nullB50
    4ToolA200ToolA200[Widget [null, qty=100], Gadget [A, qty=0], null [B, qty=50], Tool [A, qty=200]]
  11. String getCategory()

    pass 1 of 12
    205String getCategory() { return categoryA; }206Integer getQuantity() { return quantity; }
    All 12 passes — pass 1 is the card above
    passcategoryquantity
    1A
    2null
    3B
    4A
    5B
    6null
    7B
    8A
    9A
    10B
    11A
    12A200
  12. Integer getQuantity()

    pass 1 of 2
    205String getCategory() { return category; }206Integer getQuantity() { return quantity200; }
  13. Integer getQuantity()

    pass 2 of 2
    205String getCategory() { return category; }206Integer getQuantity() { return quantitynull; }
  14. numbers ← [5, null, 2, null, 8, 1, null], values ← [3.14, null, 2.71, 1.41, null]

    79// Category nulls last, then quantity nulls first80products.sort(81    Comparator.comparing(82        Product::getCategory,83        Comparator.nullsLast(String::compareTo)84    )85    .thenComparing(86        Product::getQuantity,87        Comparator.nullsFirst(Integer::compareTo)88    )89);9091System.out.println("Category/quantity null-safe:");92products.forEach(System.out::println);93System.out.println("\nAll nulls handling:");9495List<Integer> numbers→ [5, null, 2, null, 8, 1, null] = Arrays.asList(5, null, 2, null, 8, 1, null);9697System.out.println("Original: " + numbers[5, null, 2, null, 8, 1, null]);9899// Nulls first100numbers.sort(Comparator.nullsFirst(Integer::compareTo));101System.out.println("Nulls first: " + numbers[null, null, null, 1, 2, 5, 8]);102103// Nulls last104numbers.sort(Comparator.nullsLast(Integer::compareTo));105System.out.println("Nulls last: " + numbers[1, 2, 5, 8, null, null, null]);106System.out.println("\nNatural order with nulls:");107108List<Double> values→ [3.14, null, 2.71, 1.41, null] = Arrays.asList(3.14, null, 2.71, 1.41, null);109110System.out.println("Original: " + values[3.14, null, 2.71, 1.41, null]);111112// Natural order, nulls first113values.sort(Comparator.nullsFirst(Comparator.naturalOrder()));114System.out.println("Natural, nulls first: " + values[null, null, 1.41, 2.71, 3.14]);115System.out.println("\nReversed with nulls:");116117List<String> items→ [Zebra, null, Apple, Mango, null] = Arrays.asList("Zebra", null, "Apple", "Mango", null);118119System.out.println("Original: " + items[Zebra, null, Apple, Mango, null]);120121// Nulls first, descending122items.sort(123    Comparator.nullsFirst(Comparator.<String>naturalOrder().reversed())124);125System.out.println("Nulls first, desc: " + items[null, null, Zebra, Mango, Apple]);126127// Nulls last, descending128items.sort(129    Comparator.nullsLast(Comparator.<String>naturalOrder().reversed())130);131System.out.println("Nulls last, desc: " + items[Zebra, Mango, Apple, null, null]);132System.out.println("\nCustom null object handling:");133134List<Task> tasks = Arrays.asList(135    new Task("Fix bug", 1),136    null,137    new Task("Write docs", 3),138    null,139    new Task("Review", 2)140);
    outputCategory/quantity null-safe:
    
    All nulls handling:
    Original: [5, null, 2, null, 8, 1, null]
    Nulls first: [null, null, null, 1, 2, 5, 8]
    Nulls last: [1, 2, 5, 8, null, null, null]
    
    Natural order with nulls:
    Original: [3.14, null, 2.71, 1.41, null]
    Natural, nulls first: [null, null, 1.41, 2.71, 3.14]
    
    Reversed with nulls:
    Original: [Zebra, null, Apple, Mango, null]
    Nulls first, desc: [null, null, Zebra, Mango, Apple]
    Nulls last, desc: [Zebra, Mango, Apple, null, null]
    
    Custom null object handling:
  15. this.title ← Fix bug, this.priority ← 1

    pass 1 of 3
    219Task(String titleFix bug, int priority1) {220    this.title→ Fix bug = titleFix bug;221    this.priority→ 1 = priority1;222}
    All 3 passes — pass 1 is the card above
    passtitleprioritythis.titlethis.prioritytasks
    1Fix bug1Fix bug1
    2Write docs3Write docs3
    3Review2Review2[Fix bug (priority=1), null, Write docs (priority=3), null, Review (priority=2)]
  16. int getPriority()

    pass 1 of 6
    224int getPriority() { return priority3; }
    All 6 passes — pass 1 is the card above
    passpriority
    13
    21
    32
    43
    52
    61
  17. tasks.sort(Comparator.nullsLast(

    145    // Handle entire null objects146    tasks.sort(Comparator.nullsLast(147        Comparator.comparing(Task::getPriority)148    ));149150    System.out.println("\nNull objects last:");151    tasks.forEach(t -> System.out.println("  " + t));152}
    output
    Null objects last:

@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