Utilities
Comparator Interface
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.
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 + ")";
}
}
}
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]public int compare(String a, String b)
pass 1 of 514Comparator<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 pass ab1 pie apple 2 banana pie 3 banana apple 4 kiwi apple 5 kiwi pie 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:this.name ← Alice, this.age ← 30
pass 1 of 6122Person(String nameAlice, int age30) {123 this.name→ Alice = nameAlice;124 this.age→ 30 = age30;125}All 6 passes — pass 1 is the card above pass nameagethis.namethis.agepeoplebyAgebyNamestaffitemsextraValuevaluesminmax1 Alice 30 Alice 30 — — — — — — — — — 2 Bob 25 Bob 25 — — — — — — — — — 3 Charlie 35 Charlie 35 [Alice (30), Bob (25), Charlie (35)] ⟨Basic lambda B⟩ ⟨Basic lambda C⟩ — — — — — — 4 David 28 David 28 — — — — — — — — — 5 Alice 32 Alice 32 — — — — — — — — — 6 Bob 28 Bob 28 — ⟨Basic lambda B⟩ ⟨Basic lambda C⟩ [David (28), Alice (32), Bob (28)] [Zebra, Apple, Mango, Banana] 3 [5, 2, 8, 1, 9, 3] 1 9
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]public int compare(String a, String b)
pass 1 of 514Comparator<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 pass ab1 pie apple 2 banana pie 3 banana apple 4 kiwi apple 5 kiwi pie 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:this.name ← Alice, this.age ← 30
pass 1 of 6122Person(String nameAlice, int age30) {123 this.name→ Alice = nameAlice;124 this.age→ 30 = age30;125}All 6 passes — pass 1 is the card above pass nameagethis.namethis.agepeoplebyAgebyNamestaffitemsextraValuevaluesminmax1 Alice 30 Alice 30 — — — — — — — — — 2 Bob 25 Bob 25 — — — — — — — — — 3 Charlie 35 Charlie 35 [Alice (30), Bob (25), Charlie (35)] ⟨Basic lambda B⟩ ⟨Basic lambda C⟩ — — — — — — 4 David 28 David 28 — — — — — — — — — 5 Alice 32 Alice 32 — — — — — — — — — 6 Bob 28 Bob 28 — ⟨Basic lambda B⟩ ⟨Basic lambda C⟩ [David (28), Alice (32), Bob (28)] [Zebra, Apple, Mango, Banana] 0 [5, 2, 8, 1, 9, 0] 0 9
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]public int compare(String a, String b)
pass 1 of 514Comparator<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 pass ab1 pie apple 2 banana pie 3 banana apple 4 kiwi apple 5 kiwi pie 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:this.name ← Alice, this.age ← 30
pass 1 of 6122Person(String nameAlice, int age30) {123 this.name→ Alice = nameAlice;124 this.age→ 30 = age30;125}All 6 passes — pass 1 is the card above pass nameagethis.namethis.agepeoplebyAgebyNamestaffitemsextraValuevaluesminmax1 Alice 30 Alice 30 — — — — — — — — — 2 Bob 25 Bob 25 — — — — — — — — — 3 Charlie 35 Charlie 35 [Alice (30), Bob (25), Charlie (35)] ⟨Basic lambda B⟩ ⟨Basic lambda C⟩ — — — — — — 4 David 28 David 28 — — — — — — — — — 5 Alice 32 Alice 32 — — — — — — — — — 6 Bob 28 Bob 28 — ⟨Basic lambda B⟩ ⟨Basic lambda C⟩ [David (28), Alice (32), Bob (28)] [Zebra, Apple, Mango, Banana] 10 [5, 2, 8, 1, 9, 10] 1 10
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);
}
}
}
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:this.name ← Alice, this.age ← 30, this.salary ← 75000.0
pass 1 of 15158Person(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 pass nameagesalarythis.namethis.agethis.salarypeoplestaffemployeesteam1 Alice 30 75000.0 Alice 30 75000.0 — — — — 2 Bob 25 60000.0 Bob 25 60000.0 — — — — 3 Charlie 35 80000.0 Charlie 35 80000.0 [Alice (age=30, salary=$75000), Bob (age=25, salary=$60000), Charlie (age=35, salary=$80000)] — — — 4 David 28 70000.0 David 28 70000.0 — — — — 5 Alice 32 75000.0 Alice 32 75000.0 — — — — 6 Bob 28 65000.0 Bob 28 65000.0 — [David (age=28, salary=$70000), Alice (age=32, salary=$75000), Bob (age=28, salary=$65000)] — — 7 Alice 30 70000.0 Alice 30 70000.0 — — — — 8 Bob 25 70000.0 Bob 25 70000.0 — — — — 9 Charlie 30 65000.0 Charlie 30 65000.0 — — [Alice (age=30, salary=$70000), Bob (age=25, salary=$70000), Charlie (age=30, salary=$65000)] — ⋯ 4 more passes ⋯ 14 Alice 30 75000.0 Alice 30 75000.0 — — — — 15 Bob 25 60000.0 Bob 25 60000.0 — — — — int getAge()
pass 1 of 16164String getName() { return name; }165int getAge() { return age25; }166double getSalary() { return salary; }16 passes — pass 1 is the card above pass age1 25 2 30 3 35 4 25 5 35 6 30 7 25 8 30 9 25 ⋯ 5 more passes ⋯ 15 25 16 30 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:String getName()
pass 1 of 18164String getName() { return nameAlice; }165int getAge() { return age; }18 passes — pass 1 is the card above pass name1 Alice 2 David 3 Bob 4 Alice 5 Bob 6 David 7 Bob 8 Alice 9 Alice ⋯ 7 more passes ⋯ 17 Bob 18 Alice 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:double getSalary()
pass 1 of 12165int getAge() { return age; }166double getSalary() { return salary65000.0; }All 12 passes — pass 1 is the card above pass salary1 65000.0 2 75000.0 3 70000.0 4 65000.0 5 70000.0 6 75000.0 7 70000.0 8 65000.0 9 70000.0 10 70000.0 11 65000.0 12 70000.0 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:this.name ← Widget, this.price ← 29.99, this.quantity ← 100
pass 1 of 6180Product(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 pass namepricequantitythis.namethis.pricethis.quantityproductsitems1 Widget 29.99 100 Widget 29.99 100 — — 2 Gadget 49.99 50 Gadget 49.99 50 — — 3 Tool 19.99 200 Tool 19.99 200 [Widget: $29.99 (qty=100), Gadget: $49.99 (qty=50), Tool: $19.99 (qty=200)] — 4 A 20.0 100 A 20.0 100 — — 5 B 30.0 50 B 30.0 50 — — 6 C 10.0 200 C 10.0 200 — [A: $20.00 (qty=100), B: $30.00 (qty=50), C: $10.00 (qty=200)] int getQuantity()
pass 1 of 6186double getPrice() { return price; }187int getQuantity() { return quantity50; }All 6 passes — pass 1 is the card above pass quantity1 50 2 100 3 200 4 50 5 200 6 100 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:double getPrice()
pass 1 of 10186double getPrice() { return price29.99; }187int getQuantity() { return quantity; }All 10 passes — pass 1 is the card above pass price1 29.99 2 49.99 3 19.99 4 29.99 5 20.0 6 30.0 7 30.0 8 10.0 9 20.0 10 10.0 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: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: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: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: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:this.name ← Alice, this.department ← Engineering, this.age ← 30
pass 1 of 3201Employee(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 pass namedepartmentagethis.namethis.departmentthis.ageworkers1 Alice Engineering 30 Alice Engineering 30 — 2 Bob Sales 25 Bob Sales 25 — 3 Charlie Engineering 35 Charlie Engineering 35 [Alice (Engineering, 30), Bob (Sales, 25), Charlie (Engineering, 35)] String getName()
pass 1 of 2207String getName() { return nameCharlie; }String getName()
pass 2 of 2207String getName() { return nameAlice; }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: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);
}
}
}
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:this.name ← Alice, this.grade ← A, this.score ← 85
pass 1 of 7161Student(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 pass namegradescorethis.namethis.gradethis.scorestudentsroster1 Alice A 85 Alice A 85 — — 2 Bob B 92 Bob B 92 — — 3 Charlie A 78 Charlie A 78 — — 4 David B 85 David B 85 [Alice: A (85), Bob: B (92), Charlie: A (78), David: B (85)] — 5 Alice A 85 Alice A 85 — — 6 Bob B 92 Bob B 92 — — 7 Charlie A 95 Charlie A 95 — [Alice: A (85), Bob: B (92), Charlie: A (95)] String getGrade()
pass 1 of 20167String getGrade() { return gradeB; }168int getScore() { return score; }20 passes — pass 1 is the card above pass grade1 B 2 A 3 A 4 B 5 A 6 B 7 A 8 A 9 B ⋯ 9 more passes ⋯ 19 B 20 A int getScore()
pass 1 of 6167String getGrade() { return grade; }168int getScore() { return score78; }All 6 passes — pass 1 is the card above pass score1 78 2 85 3 85 4 92 5 85 6 95 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:this.name ← Alice, this.department ← Engineering, this.age ← 30
pass 1 of 4182Employee(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 pass namedepartmentagesalarythis.namethis.departmentthis.agethis.salaryemployees1 Alice Engineering 30 75000.0 Alice Engineering 30 75000.0 — 2 Bob Engineering 30 70000.0 Bob Engineering 30 70000.0 — 3 Charlie Sales 25 60000.0 Charlie Sales 25 60000.0 — 4 David Engineering 25 65000.0 David Engineering 25 65000.0 [Alice (Engineering, 30, $75000), Bob (Engineering, 30, $70000), Charlie (Sales, 25, $60000), David (Engineering, 25, $65000)] String getDepartment()
pass 1 of 16189String getDepartment() { return departmentEngineering; }190int getAge() { return age; }16 passes — pass 1 is the card above pass department1 Engineering 2 Engineering 3 Sales 4 Engineering 5 Sales 6 Engineering 7 Engineering 8 Engineering 9 Engineering ⋯ 5 more passes ⋯ 15 Sales 16 Engineering int getAge()
pass 1 of 10189String getDepartment() { return department; }190int getAge() { return age30; }191double getSalary() { return salary; }All 10 passes — pass 1 is the card above pass age1 30 2 30 3 25 4 30 5 25 6 30 7 30 8 25 9 30 10 30 double getSalary()
pass 1 of 4190int getAge() { return age; }191double getSalary() { return salary70000.0; }All 4 passes — pass 1 is the card above pass salary1 70000.0 2 75000.0 3 75000.0 4 70000.0 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:this.name ← Widget, this.category ← A, this.price ← 29.99, this.quantity ← 100
pass 1 of 4206Product(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 pass namecategorypricequantitythis.namethis.categorythis.pricethis.quantityproducts1 Widget A 29.99 100 Widget A 29.99 100 — 2 Gadget B 49.99 50 Gadget B 49.99 50 — 3 Tool A 19.99 200 Tool A 19.99 200 — 4 Device B 39.99 150 Device B 39.99 150 [Widget [A]: $29.99, Gadget [B]: $49.99, Tool [A]: $19.99, Device [B]: $39.99] String getCategory()
pass 1 of 12213String getCategory() { return categoryB; }214double getPrice() { return price; }All 12 passes — pass 1 is the card above pass category1 B 2 A 3 A 4 B 5 A 6 B 7 A 8 A 9 B 10 A 11 B 12 B double getPrice()
pass 1 of 4213String getCategory() { return category; }214double getPrice() { return price29.99; }All 4 passes — pass 1 is the card above pass price1 29.99 2 19.99 3 49.99 4 39.99 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: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:this.title ← Fix bug, this.priority ← High, this.days ← 2
pass 1 of 4227Task(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 pass titleprioritydaysthis.titlethis.prioritythis.daystasks1 Fix bug High 2 Fix bug High 2 — 2 Write docs Low 1 Write docs Low 1 — 3 Review code Medium 3 Review code Medium 3 — 4 Testing High 1 Testing High 1 [Fix bug [High, 2d], Write docs [Low, 1d], Review code [Medium, 3d], Testing [High, 1d]] String getPriority()
pass 1 of 12233String getPriority() { return priorityLow; }234int getDays() { return days; }All 12 passes — pass 1 is the card above pass prioritydays1 Low — 2 High — 3 Medium — 4 Low — 5 Medium — 6 Low — 7 Medium — 8 High — 9 High — 10 Medium — 11 High — 12 High 1 int getDays()
pass 1 of 2233String getPriority() { return priority; }234int getDays() { return days1; }int getDays()
pass 2 of 2233String getPriority() { return priority; }234int getDays() { return days2; }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:this.name ← Alice, this.department ← Engineering
pass 1 of 4246Person(String nameAlice, String departmentEngineering) {247 this.name→ Alice = nameAlice;248 this.department→ Engineering = departmentEngineering;249}All 4 passes — pass 1 is the card above pass namedepartmentthis.namethis.departmentpeople1 Alice Engineering Alice Engineering — 2 Bob null Bob null — 3 Charlie Sales Charlie Sales — 4 David null David null [Alice (Engineering), Bob (null), Charlie (Sales), David (null)] String getDepartment()
pass 1 of 12251String getName() { return name; }252String getDepartment() { return departmentnull; }All 12 passes — pass 1 is the card above pass departmentname1 null — 2 Engineering — 3 Sales — 4 null — 5 Sales — 6 null — 7 Sales — 8 Engineering — 9 null — 10 Sales — 11 null — 12 null David String getName()
pass 1 of 2251String getName() { return nameDavid; }252String getDepartment() { return department; }String getName()
pass 2 of 2251String getName() { return nameBob; }252String getDepartment() { return department; }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:this.id ← 1, this.category ← A, this.value ← 100
pass 1 of 4265Record(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 pass idcategoryvaluethis.idthis.categorythis.valuerecords1 1 A 100 1 A 100 — 2 2 B 100 2 B 100 — 3 3 A 200 3 A 200 — 4 4 B 100 4 B 100 [#1: A=100, #2: B=100, #3: A=200, #4: B=100] String getCategory()
pass 1 of 12271String getCategory() { return categoryB; }All 12 passes — pass 1 is the card above pass category1 B 2 A 3 A 4 B 5 A 6 B 7 A 8 A 9 B 10 A 11 B 12 B 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: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);
}
}
}
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:this.name ← Alice, this.age ← 30
pass 1 of 3172Person(String nameAlice, int age30) {173 this.name→ Alice = nameAlice;174 this.age→ 30 = age30;175}All 3 passes — pass 1 is the card above pass nameagethis.namethis.agepeople1 Alice 30 Alice 30 — 2 Bob 25 Bob 25 — 3 Charlie 35 Charlie 35 [Alice (30), Bob (25), Charlie (35)] int getAge()
pass 1 of 10177int getAge() { return age25; }All 10 passes — pass 1 is the card above pass age1 25 2 30 3 35 4 25 5 35 6 30 7 25 8 30 9 30 10 35 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: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:this.name ← Widget, this.price ← 29.99
pass 1 of 3189Product(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 pass namepricethis.namethis.priceproducts1 Widget 29.99 Widget 29.99 — 2 Gadget 49.99 Gadget 49.99 — 3 Tool 19.99 Tool 19.99 [Widget: $29.99, Gadget: $49.99, Tool: $19.99] @Override public int compareTo(Product other)
pass 1 of 6194@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 pass otherthis.priceother.price1 Widget: $29.99 49.99 29.99 2 Gadget: $49.99 19.99 49.99 3 Gadget: $49.99 19.99 49.99 4 Widget: $29.99 19.99 29.99 5 Widget: $29.99 19.99 29.99 6 Gadget: $49.99 29.99 49.99 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: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:this.name ← Alice, this.department ← Engineering, this.salary ← 75000.0
pass 1 of 3210Employee(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 pass namedepartmentsalarythis.namethis.departmentthis.salaryemployees1 Alice Engineering 75000.0 Alice Engineering 75000.0 — 2 Bob Sales 60000.0 Bob Sales 60000.0 — 3 Charlie Engineering 80000.0 Charlie Engineering 80000.0 [Alice (Engineering, $75000), Bob (Sales, $60000), Charlie (Engineering, $80000)] String getDepartment()
pass 1 of 8216String getDepartment() { return departmentSales; }217double getSalary() { return salary; }All 8 passes — pass 1 is the card above pass departmentsalary1 Sales — 2 Engineering — 3 Engineering — 4 Sales — 5 Engineering — 6 Sales — 7 Engineering — 8 Engineering 75000.0 double getSalary()
pass 1 of 2216String getDepartment() { return department; }217double getSalary() { return salary75000.0; }double getSalary()
pass 2 of 2216String getDepartment() { return department; }217double getSalary() { return salary80000.0; }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:this.name ← Alice, this.grade ← A, this.score ← 85
pass 1 of 3230Student(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 pass namegradescorethis.namethis.gradethis.scorestudents1 Alice A 85 Alice A 85 — 2 Bob B 92 Bob B 92 — 3 Charlie A 95 Charlie A 95 [Alice: A (85), Bob: B (92), Charlie: A (95)] String getGrade()
pass 1 of 12236String getGrade() { return gradeB; }237int getScore() { return score; }All 12 passes — pass 1 is the card above pass grade1 B 2 A 3 A 4 B 5 A 6 B 7 A 8 A 9 A 10 A 11 A 12 B int getScore()
pass 1 of 4236String getGrade() { return grade; }237int getScore() { return score95; }All 4 passes — pass 1 is the card above pass score1 95 2 85 3 85 4 95 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: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);
}
}
}
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:this.name ← Alice, this.age ← 30
pass 1 of 4158Person(String nameAlice, Integer age30) {159 this.name→ Alice = nameAlice;160 this.age→ 30 = age30;161}All 4 passes — pass 1 is the card above pass nameagethis.namethis.agepeople1 Alice 30 Alice 30 — 2 null 25 null 25 — 3 Charlie 35 Charlie 35 — 4 Bob null Bob null [Alice (30), null (25), Charlie (35), Bob (null)] String getName()
pass 1 of 12163String getName() { return namenull; }164Integer getAge() { return age; }All 12 passes — pass 1 is the card above pass name1 null 2 Alice 3 Charlie 4 null 5 Charlie 6 null 7 Charlie 8 Alice 9 Bob 10 Charlie 11 Bob 12 Alice 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:this.name ← Alice, this.department ← Engineering, this.salary ← null
pass 1 of 4177Employee(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 pass namedepartmentsalarythis.namethis.departmentthis.salaryemployees1 Alice Engineering null Alice Engineering null — 2 null Sales 60000.0 null Sales 60000.0 — 3 Charlie null 80000.0 Charlie null 80000.0 — 4 Bob Engineering 70000.0 Bob Engineering 70000.0 [Alice (Engineering, $0), null (Sales, $60000), Charlie (null, $80000), Bob (Engineering, $70000)] String getDepartment()
pass 1 of 10183String getName() { return name; }184String getDepartment() { return departmentSales; }185Double getSalary() { return salary; }All 10 passes — pass 1 is the card above pass departmentname1 Sales — 2 Engineering — 3 null — 4 Sales — 5 Engineering — 6 null — 7 Engineering — 8 Sales — 9 Engineering — 10 Engineering Bob String getName()
pass 1 of 2183String getName() { return nameBob; }184String getDepartment() { return department; }String getName()
pass 2 of 2183String getName() { return nameAlice; }184String getDepartment() { return department; }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:this.name ← Widget, this.category ← null, this.quantity ← 100
pass 1 of 4199Product(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 pass namecategoryquantitythis.namethis.categorythis.quantityproducts1 Widget null 100 Widget null 100 — 2 Gadget A null Gadget A null — 3 null B 50 null B 50 — 4 Tool A 200 Tool A 200 [Widget [null, qty=100], Gadget [A, qty=0], null [B, qty=50], Tool [A, qty=200]] String getCategory()
pass 1 of 12205String getCategory() { return categoryA; }206Integer getQuantity() { return quantity; }All 12 passes — pass 1 is the card above pass categoryquantity1 A — 2 null — 3 B — 4 A — 5 B — 6 null — 7 B — 8 A — 9 A — 10 B — 11 A — 12 A 200 Integer getQuantity()
pass 1 of 2205String getCategory() { return category; }206Integer getQuantity() { return quantity200; }Integer getQuantity()
pass 2 of 2205String getCategory() { return category; }206Integer getQuantity() { return quantitynull; }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:this.title ← Fix bug, this.priority ← 1
pass 1 of 3219Task(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 pass titleprioritythis.titlethis.prioritytasks1 Fix bug 1 Fix bug 1 — 2 Write docs 3 Write docs 3 — 3 Review 2 Review 2 [Fix bug (priority=1), null, Write docs (priority=3), null, Review (priority=2)] int getPriority()
pass 1 of 6224int getPriority() { return priority3; }All 6 passes — pass 1 is the card above pass priority1 3 2 1 3 2 4 3 5 2 6 1 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