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.
Basic Comparator
Create simple comparators using lambdas.
Basic.java
// Basic Comparator examples
import java.util.*;
public class Basic {
public static void main(String[] args) {
System.out.println("String length comparator:");
List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");
System.out.println("Original: " + words);
// Sort by length
Comparator<String> byLength = new Comparator<String>() {
public int compare(String a, String b) {
return a.length() - b.length();
}
};
words.sort(byLength);
System.out.println("By length: " + words);
System.out.println("\nLambda syntax:");
List<String> fruits = Arrays.asList("strawberry", "fig", "apple", "pear");
System.out.println("Original: " + fruits);
// Lambda comparator
fruits.sort((a, b) -> a.length() - b.length());
System.out.println("By length: " + fruits);
// Reverse length
fruits.sort((a, b) -> b.length() - a.length());
System.out.println("Reverse length: " + fruits);
System.out.println("\nCase-insensitive:");
List<String> names = Arrays.asList("Alice", "bob", "Charlie", "david");
System.out.println("Original: " + names);
names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));
System.out.println("Case-insensitive: " + names);
System.out.println("\nNumbers:");
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
System.out.println("Original: " + numbers);
// Ascending
numbers.sort((a, b) -> a - b);
System.out.println("Ascending: " + numbers);
// Descending
numbers.sort((a, b) -> b - a);
System.out.println("Descending: " + numbers);
System.out.println("\nCustom objects:");
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
System.out.println("Original:");
people.forEach(System.out::println);
// Sort by age
people.sort((a, b) -> a.age - b.age);
System.out.println("\nBy age:");
people.forEach(System.out::println);
// Sort by name
people.sort((a, b) -> a.name.compareTo(b.name));
System.out.println("\nBy name:");
people.forEach(System.out::println);
System.out.println("\nComparator variable:");
Comparator<Person> byAge = (a, b) -> a.age - b.age;
Comparator<Person> byName = (a, b) -> a.name.compareTo(b.name);
List<Person> staff = Arrays.asList(
new Person("David", 28),
new Person("Alice", 32),
new Person("Bob", 28)
);
System.out.println("Sort by age:");
staff.sort(byAge);
staff.forEach(System.out::println);
System.out.println("\nSort by name:");
staff.sort(byName);
staff.forEach(System.out::println);
System.out.println("\nWith Collections:");
List<String> items = Arrays.asList("Zebra", "Apple", "Mango", "Banana");
System.out.println("Original: " + items);
Collections.sort(items, (a, b) -> a.compareTo(b));
System.out.println("Sorted: " + items);
Collections.sort(items, (a, b) -> b.compareTo(a));
System.out.println("Reversed: " + items);
System.out.println("\nMin/Max:");
int extraValue = ;
List<Integer> values = Arrays.asList(5, 2, 8, 1, 9, extraValue);
int min = Collections.min(values, (a, b) -> a - b);
int max = Collections.max(values, (a, b) -> a - b);
System.out.println("Values: " + values);
System.out.println("Min: " + min);
System.out.println("Max: " + max);
}
static class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
}
// Basic Comparator examples
import java.util.*;
public class Basic {
public static void main(String[] args) {
System.out.println("String length comparator:");
List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");
System.out.println("Original: " + words);
// Sort by length
Comparator<String> byLength = new Comparator<String>() {
public int compare(String a, String b) {
return a.length() - b.length();
}
};
words.sort(byLength);
System.out.println("By length: " + words);
System.out.println("\nLambda syntax:");
List<String> fruits = Arrays.asList("strawberry", "fig", "apple", "pear");
System.out.println("Original: " + fruits);
// Lambda comparator
fruits.sort((a, b) -> a.length() - b.length());
System.out.println("By length: " + fruits);
// Reverse length
fruits.sort((a, b) -> b.length() - a.length());
System.out.println("Reverse length: " + fruits);
System.out.println("\nCase-insensitive:");
List<String> names = Arrays.asList("Alice", "bob", "Charlie", "david");
System.out.println("Original: " + names);
names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));
System.out.println("Case-insensitive: " + names);
System.out.println("\nNumbers:");
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
System.out.println("Original: " + numbers);
// Ascending
numbers.sort((a, b) -> a - b);
System.out.println("Ascending: " + numbers);
// Descending
numbers.sort((a, b) -> b - a);
System.out.println("Descending: " + numbers);
System.out.println("\nCustom objects:");
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
System.out.println("Original:");
people.forEach(System.out::println);
// Sort by age
people.sort((a, b) -> a.age - b.age);
System.out.println("\nBy age:");
people.forEach(System.out::println);
// Sort by name
people.sort((a, b) -> a.name.compareTo(b.name));
System.out.println("\nBy name:");
people.forEach(System.out::println);
System.out.println("\nComparator variable:");
Comparator<Person> byAge = (a, b) -> a.age - b.age;
Comparator<Person> byName = (a, b) -> a.name.compareTo(b.name);
List<Person> staff = Arrays.asList(
new Person("David", 28),
new Person("Alice", 32),
new Person("Bob", 28)
);
System.out.println("Sort by age:");
staff.sort(byAge);
staff.forEach(System.out::println);
System.out.println("\nSort by name:");
staff.sort(byName);
staff.forEach(System.out::println);
System.out.println("\nWith Collections:");
List<String> items = Arrays.asList("Zebra", "Apple", "Mango", "Banana");
System.out.println("Original: " + items);
Collections.sort(items, (a, b) -> a.compareTo(b));
System.out.println("Sorted: " + items);
Collections.sort(items, (a, b) -> b.compareTo(a));
System.out.println("Reversed: " + items);
System.out.println("\nMin/Max:");
int extraValue = ;
List<Integer> values = Arrays.asList(5, 2, 8, 1, 9, extraValue);
int min = Collections.min(values, (a, b) -> a - b);
int max = Collections.max(values, (a, b) -> a - b);
System.out.println("Values: " + values);
System.out.println("Min: " + min);
System.out.println("Max: " + max);
}
static class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
}
// Basic Comparator examples
import java.util.*;
public class Basic {
public static void main(String[] args) {
System.out.println("String length comparator:");
List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");
System.out.println("Original: " + words);
// Sort by length
Comparator<String> byLength = new Comparator<String>() {
public int compare(String a, String b) {
return a.length() - b.length();
}
};
words.sort(byLength);
System.out.println("By length: " + words);
System.out.println("\nLambda syntax:");
List<String> fruits = Arrays.asList("strawberry", "fig", "apple", "pear");
System.out.println("Original: " + fruits);
// Lambda comparator
fruits.sort((a, b) -> a.length() - b.length());
System.out.println("By length: " + fruits);
// Reverse length
fruits.sort((a, b) -> b.length() - a.length());
System.out.println("Reverse length: " + fruits);
System.out.println("\nCase-insensitive:");
List<String> names = Arrays.asList("Alice", "bob", "Charlie", "david");
System.out.println("Original: " + names);
names.sort((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()));
System.out.println("Case-insensitive: " + names);
System.out.println("\nNumbers:");
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
System.out.println("Original: " + numbers);
// Ascending
numbers.sort((a, b) -> a - b);
System.out.println("Ascending: " + numbers);
// Descending
numbers.sort((a, b) -> b - a);
System.out.println("Descending: " + numbers);
System.out.println("\nCustom objects:");
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
System.out.println("Original:");
people.forEach(System.out::println);
// Sort by age
people.sort((a, b) -> a.age - b.age);
System.out.println("\nBy age:");
people.forEach(System.out::println);
// Sort by name
people.sort((a, b) -> a.name.compareTo(b.name));
System.out.println("\nBy name:");
people.forEach(System.out::println);
System.out.println("\nComparator variable:");
Comparator<Person> byAge = (a, b) -> a.age - b.age;
Comparator<Person> byName = (a, b) -> a.name.compareTo(b.name);
List<Person> staff = Arrays.asList(
new Person("David", 28),
new Person("Alice", 32),
new Person("Bob", 28)
);
System.out.println("Sort by age:");
staff.sort(byAge);
staff.forEach(System.out::println);
System.out.println("\nSort by name:");
staff.sort(byName);
staff.forEach(System.out::println);
System.out.println("\nWith Collections:");
List<String> items = Arrays.asList("Zebra", "Apple", "Mango", "Banana");
System.out.println("Original: " + items);
Collections.sort(items, (a, b) -> a.compareTo(b));
System.out.println("Sorted: " + items);
Collections.sort(items, (a, b) -> b.compareTo(a));
System.out.println("Reversed: " + items);
System.out.println("\nMin/Max:");
int extraValue = ;
List<Integer> values = Arrays.asList(5, 2, 8, 1, 9, extraValue);
int min = Collections.min(values, (a, b) -> a - b);
int max = Collections.max(values, (a, b) -> a - b);
System.out.println("Values: " + values);
System.out.println("Min: " + min);
System.out.println("Max: " + max);
}
static class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
}
Comparator
A functional interface defining comparison logic externally, allowing multiple sort orders for the same class.
Comparison contract
Return negative if first is less, zero if equal, positive if first is greater. Avoid subtraction for int comparison due to overflow.
Using Comparator.comparing()
Create comparators declaratively from method references.
Comparing.java
// Comparator.comparing examples
import java.util.*;
import java.util.function.Function;
public class Comparing {
public static void main(String[] args) {
System.out.println("Basic comparing:");
List<Person> people = Arrays.asList(
new Person("Alice", 30, 75000),
new Person("Bob", 25, 60000),
new Person("Charlie", 35, 80000)
);
System.out.println("Original:");
people.forEach(System.out::println);
// Sort by age using method reference
people.sort(Comparator.comparing(Person::getAge));
System.out.println("\nBy age:");
people.forEach(System.out::println);
System.out.println("\nDifferent keys:");
List<Person> staff = Arrays.asList(
new Person("David", 28, 70000),
new Person("Alice", 32, 75000),
new Person("Bob", 28, 65000)
);
// Sort by name
staff.sort(Comparator.comparing(Person::getName));
System.out.println("By name:");
staff.forEach(System.out::println);
// Sort by salary
staff.sort(Comparator.comparing(Person::getSalary));
System.out.println("\nBy salary:");
staff.forEach(System.out::println);
System.out.println("\nLambda extractor:");
List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");
// Sort by length (lambda)
words.sort(Comparator.comparing(s -> s.length()));
System.out.println("By length: " + words);
// Sort by last character
words.sort(Comparator.comparing(s -> s.charAt(s.length() - 1)));
System.out.println("By last char: " + words);
System.out.println("\nComparingInt/Long/Double:");
List<Product> products = Arrays.asList(
new Product("Widget", 29.99, 100),
new Product("Gadget", 49.99, 50),
new Product("Tool", 19.99, 200)
);
// comparingInt for primitives (more efficient)
products.sort(Comparator.comparingInt(Product::getQuantity));
System.out.println("By quantity:");
products.forEach(System.out::println);
// comparingDouble for prices
products.sort(Comparator.comparingDouble(Product::getPrice));
System.out.println("\nBy price:");
products.forEach(System.out::println);
System.out.println("\nChained comparators:");
List<Person> employees = Arrays.asList(
new Person("Alice", 30, 70000),
new Person("Bob", 25, 70000),
new Person("Charlie", 30, 65000)
);
// Sort by salary, then by age
employees.sort(
Comparator.comparing(Person::getSalary)
.thenComparing(Person::getAge)
);
System.out.println("By salary, then age:");
employees.forEach(System.out::println);
System.out.println("\nThenComparing variants:");
List<Person> team = Arrays.asList(
new Person("Alice", 30, 70000),
new Person("Alice", 25, 60000),
new Person("Bob", 30, 70000)
);
// By name, then age
team.sort(
Comparator.comparing(Person::getName)
.thenComparingInt(Person::getAge)
);
System.out.println("By name, then age:");
team.forEach(System.out::println);
// Three levels
team.sort(
Comparator.comparing(Person::getName)
.thenComparingInt(Person::getAge)
.thenComparingDouble(Person::getSalary)
);
System.out.println("\nBy name, age, salary:");
team.forEach(System.out::println);
System.out.println("\nReverse order:");
List<Product> items = Arrays.asList(
new Product("A", 20.0, 100),
new Product("B", 30.0, 50),
new Product("C", 10.0, 200)
);
// Descending price
items.sort(Comparator.comparing(Product::getPrice).reversed());
System.out.println("Price descending:");
items.forEach(System.out::println);
System.out.println("\nCustom extraction:");
List<Employee> workers = Arrays.asList(
new Employee("Alice", "Engineering", 30),
new Employee("Bob", "Sales", 25),
new Employee("Charlie", "Engineering", 35)
);
// Sort by department length, then name
workers.sort(
Comparator.comparing((Employee e) -> e.department.length())
.thenComparing(Employee::getName)
);
System.out.println("By dept length, then name:");
workers.forEach(System.out::println);
System.out.println("\nStream sorting:");
List<Person> sorted = Arrays.asList(
new Person("Charlie", 35, 80000),
new Person("Alice", 30, 75000),
new Person("Bob", 25, 60000)
)
.stream()
.sorted(Comparator.comparing(Person::getAge))
.toList();
System.out.println("Sorted stream:");
sorted.forEach(System.out::println);
}
static class Person {
private String name;
private int age;
private double salary;
Person(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
String getName() { return name; }
int getAge() { return age; }
double getSalary() { return salary; }
@Override
public String toString() {
return String.format("%s (age=%d, salary=$%.0f)",
name, age, salary);
}
}
static class Product {
private String name;
private double price;
private int quantity;
Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
double getPrice() { return price; }
int getQuantity() { return quantity; }
@Override
public String toString() {
return String.format("%s: $%.2f (qty=%d)",
name, price, quantity);
}
}
static class Employee {
private String name;
private String department;
private int age;
Employee(String name, String department, int age) {
this.name = name;
this.department = department;
this.age = age;
}
String getName() { return name; }
@Override
public String toString() {
return String.format("%s (%s, %d)", name, department, age);
}
}
}
Key extractor
Comparator.comparing() takes a function that extracts the sort key, making comparator creation concise and readable.
Chained Comparators
Sort by multiple fields with thenComparing().
Chained.java
// Chained comparators examples
import java.util.*;
public class Chained {
public static void main(String[] args) {
System.out.println("Two-level sort:");
List<Student> students = Arrays.asList(
new Student("Alice", "A", 85),
new Student("Bob", "B", 92),
new Student("Charlie", "A", 78),
new Student("David", "B", 85)
);
System.out.println("Original:");
students.forEach(System.out::println);
// Sort by grade, then score
students.sort(
Comparator.comparing(Student::getGrade)
.thenComparingInt(Student::getScore)
);
System.out.println("\nBy grade, then score:");
students.forEach(System.out::println);
System.out.println("\nThree-level sort:");
List<Employee> employees = Arrays.asList(
new Employee("Alice", "Engineering", 30, 75000),
new Employee("Bob", "Engineering", 30, 70000),
new Employee("Charlie", "Sales", 25, 60000),
new Employee("David", "Engineering", 25, 65000)
);
// Sort by department, age, then salary
employees.sort(
Comparator.comparing(Employee::getDepartment)
.thenComparingInt(Employee::getAge)
.thenComparingDouble(Employee::getSalary)
);
System.out.println("By dept, age, salary:");
employees.forEach(System.out::println);
System.out.println("\nMixed ascending/descending:");
List<Product> products = Arrays.asList(
new Product("Widget", "A", 29.99, 100),
new Product("Gadget", "B", 49.99, 50),
new Product("Tool", "A", 19.99, 200),
new Product("Device", "B", 39.99, 150)
);
// Category ascending, price descending
products.sort(
Comparator.comparing(Product::getCategory)
.thenComparing(
Comparator.comparingDouble(Product::getPrice).reversed()
)
);
System.out.println("Category asc, price desc:");
products.forEach(System.out::println);
System.out.println("\nReverse entire chain:");
List<Student> roster = Arrays.asList(
new Student("Alice", "A", 85),
new Student("Bob", "B", 92),
new Student("Charlie", "A", 95)
);
// Reverse the entire sort
roster.sort(
Comparator.comparing(Student::getGrade)
.thenComparingInt(Student::getScore)
.reversed()
);
System.out.println("Reversed grade/score:");
roster.forEach(System.out::println);
System.out.println("\nCustom order:");
Map<String, Integer> priorityOrder = Map.of(
"High", 1,
"Medium", 2,
"Low", 3
);
List<Task> tasks = Arrays.asList(
new Task("Fix bug", "High", 2),
new Task("Write docs", "Low", 1),
new Task("Review code", "Medium", 3),
new Task("Testing", "High", 1)
);
// Sort by priority order, then days
tasks.sort(
Comparator.comparing((Task t) -> priorityOrder.get(t.getPriority()))
.thenComparingInt(Task::getDays)
);
System.out.println("By priority, then days:");
tasks.forEach(System.out::println);
System.out.println("\nNull handling:");
List<Person> people = Arrays.asList(
new Person("Alice", "Engineering"),
new Person("Bob", null),
new Person("Charlie", "Sales"),
new Person("David", null)
);
// Nulls last, then alphabetic
people.sort(
Comparator.comparing(
Person::getDepartment,
Comparator.nullsLast(String::compareTo)
)
.thenComparing(Person::getName)
);
System.out.println("Dept (nulls last), then name:");
people.forEach(System.out::println);
System.out.println("\nStable sort:");
List<Record> records = Arrays.asList(
new Record(1, "A", 100),
new Record(2, "B", 100),
new Record(3, "A", 200),
new Record(4, "B", 100)
);
System.out.println("Original order:");
records.forEach(System.out::println);
// Sort by category only - original order preserved within category
records.sort(Comparator.comparing(Record::getCategory));
System.out.println("\nBy category (stable):");
records.forEach(System.out::println);
System.out.println("\nPerformance optimization:");
// Reuse comparators
Comparator<Employee> byDept = Comparator.comparing(Employee::getDepartment);
Comparator<Employee> byAge = Comparator.comparingInt(Employee::getAge);
Comparator<Employee> bySalary = Comparator.comparingDouble(Employee::getSalary);
Comparator<Employee> combined = byDept.thenComparing(byAge).thenComparing(bySalary);
employees.sort(combined);
System.out.println("Using reusable comparators:");
employees.forEach(System.out::println);
}
static class Student {
private String name;
private String grade;
private int score;
Student(String name, String grade, int score) {
this.name = name;
this.grade = grade;
this.score = score;
}
String getGrade() { return grade; }
int getScore() { return score; }
@Override
public String toString() {
return String.format("%s: %s (%d)", name, grade, score);
}
}
static class Employee {
private String name;
private String department;
private int age;
private double salary;
Employee(String name, String department, int age, double salary) {
this.name = name;
this.department = department;
this.age = age;
this.salary = salary;
}
String getDepartment() { return department; }
int getAge() { return age; }
double getSalary() { return salary; }
@Override
public String toString() {
return String.format("%s (%s, %d, $%.0f)",
name, department, age, salary);
}
}
static class Product {
private String name;
private String category;
private double price;
private int quantity;
Product(String name, String category, double price, int quantity) {
this.name = name;
this.category = category;
this.price = price;
this.quantity = quantity;
}
String getCategory() { return category; }
double getPrice() { return price; }
@Override
public String toString() {
return String.format("%s [%s]: $%.2f", name, category, price);
}
}
static class Task {
private String title;
private String priority;
private int days;
Task(String title, String priority, int days) {
this.title = title;
this.priority = priority;
this.days = days;
}
String getPriority() { return priority; }
int getDays() { return days; }
@Override
public String toString() {
return String.format("%s [%s, %dd]", title, priority, days);
}
}
static class Person {
private String name;
private String department;
Person(String name, String department) {
this.name = name;
this.department = department;
}
String getName() { return name; }
String getDepartment() { return department; }
@Override
public String toString() {
return String.format("%s (%s)", name, department);
}
}
static class Record {
private int id;
private String category;
private int value;
Record(int id, String category, int value) {
this.id = id;
this.category = category;
this.value = value;
}
String getCategory() { return category; }
@Override
public String toString() {
return String.format("#%d: %s=%d", id, category, value);
}
}
}
Secondary sort
Use thenComparing() to break ties, like sorting by last name then first name.
Reverse Ordering
Invert sort order easily.
Reverse.java
// Reverse order comparators
import java.util.*;
public class Reverse {
public static void main(String[] args) {
System.out.println("Basic reversed:");
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);
System.out.println("Original: " + numbers);
// Ascending (natural order)
numbers.sort(Comparator.naturalOrder());
System.out.println("Ascending: " + numbers);
// Descending
numbers.sort(Comparator.reverseOrder());
System.out.println("Descending: " + numbers);
System.out.println("\nReversed on custom:");
List<String> words = Arrays.asList("apple", "pie", "banana", "kiwi");
System.out.println("Original: " + words);
// By length ascending
words.sort(Comparator.comparing(String::length));
System.out.println("By length asc: " + words);
// By length descending
words.sort(Comparator.comparing(String::length).reversed());
System.out.println("By length desc: " + words);
System.out.println("\nReverse natural order:");
List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");
System.out.println("Original: " + names);
// Natural order (alphabetic)
names.sort(Comparator.naturalOrder());
System.out.println("Alphabetic: " + names);
// Reverse alphabetic
names.sort(Comparator.reverseOrder());
System.out.println("Reverse alpha: " + names);
// Using reversed() - need explicit type parameter for type inference
names.sort(Comparator.<String>naturalOrder().reversed());
System.out.println("Using reversed(): " + names);
System.out.println("\nCustom objects:");
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
System.out.println("Original:");
people.forEach(System.out::println);
// By age ascending
people.sort(Comparator.comparing(Person::getAge));
System.out.println("\nAge ascending:");
people.forEach(System.out::println);
// By age descending
people.sort(Comparator.comparing(Person::getAge).reversed());
System.out.println("\nAge descending:");
people.forEach(System.out::println);
System.out.println("\nComparable reversed:");
List<Product> products = Arrays.asList(
new Product("Widget", 29.99),
new Product("Gadget", 49.99),
new Product("Tool", 19.99)
);
System.out.println("Original:");
products.forEach(System.out::println);
// Natural order (by price, defined in Product)
Collections.sort(products);
System.out.println("\nNatural order:");
products.forEach(System.out::println);
// Reverse natural order
Collections.sort(products, Comparator.reverseOrder());
System.out.println("\nReverse natural:");
products.forEach(System.out::println);
System.out.println("\nChained with reversed:");
List<Employee> employees = Arrays.asList(
new Employee("Alice", "Engineering", 75000),
new Employee("Bob", "Sales", 60000),
new Employee("Charlie", "Engineering", 80000)
);
// Dept ascending, salary descending
employees.sort(
Comparator.comparing(Employee::getDepartment)
.thenComparing(
Comparator.comparingDouble(Employee::getSalary).reversed()
)
);
System.out.println("Dept asc, salary desc:");
employees.forEach(System.out::println);
System.out.println("\nReversing entire chain:");
List<Student> students = Arrays.asList(
new Student("Alice", "A", 85),
new Student("Bob", "B", 92),
new Student("Charlie", "A", 95)
);
// Normal: grade, then score
students.sort(
Comparator.comparing(Student::getGrade)
.thenComparingInt(Student::getScore)
);
System.out.println("Normal order:");
students.forEach(System.out::println);
// Reversed: descending grade and score
students.sort(
Comparator.comparing(Student::getGrade)
.thenComparingInt(Student::getScore)
.reversed()
);
System.out.println("\nReversed order:");
students.forEach(System.out::println);
System.out.println("\nCollections methods:");
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
System.out.println("Original: " + nums);
// Collections.reverse (in-place)
Collections.reverse(nums);
System.out.println("After reverse(): " + nums);
// Sort ascending
Collections.sort(nums);
System.out.println("After sort(): " + nums);
// Sort descending
Collections.sort(nums, Comparator.reverseOrder());
System.out.println("Reverse order sort: " + nums);
System.out.println("\nMin/Max with reverse:");
List<Double> prices = Arrays.asList(19.99, 29.99, 9.99, 39.99);
// Min with natural order
double min = Collections.min(prices);
System.out.println("Min: $" + min);
// Max with natural order
double max = Collections.max(prices);
System.out.println("Max: $" + max);
// "Min" with reverse order (actually max)
double reverseMin = Collections.min(prices, Comparator.reverseOrder());
System.out.println("Min with reverseOrder: $" + reverseMin);
}
static class Person {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
int getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
static class Product implements Comparable<Product> {
private String name;
private double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public int compareTo(Product other) {
return Double.compare(this.price, other.price);
}
@Override
public String toString() {
return String.format("%s: $%.2f", name, price);
}
}
static class Employee {
private String name;
private String department;
private double salary;
Employee(String name, String department, double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
String getDepartment() { return department; }
double getSalary() { return salary; }
@Override
public String toString() {
return String.format("%s (%s, $%.0f)", name, department, salary);
}
}
static class Student {
private String name;
private String grade;
private int score;
Student(String name, String grade, int score) {
this.name = name;
this.grade = grade;
this.score = score;
}
String getGrade() { return grade; }
int getScore() { return score; }
@Override
public String toString() {
return String.format("%s: %s (%d)", name, grade, score);
}
}
}
Null-Safe Comparators
Handle null values in comparisons gracefully.
Nullsafe.java
// Null-safe comparators
import java.util.*;
public class Nullsafe {
public static void main(String[] args) {
System.out.println("NullsFirst:");
List<String> names = Arrays.asList("Alice", null, "Charlie", "Bob", null);
System.out.println("Original: " + names);
// Nulls first, then natural order
names.sort(Comparator.nullsFirst(String::compareTo));
System.out.println("Nulls first: " + names);
System.out.println("\nNullsLast:");
List<String> words = Arrays.asList("apple", null, "banana", null, "cherry");
System.out.println("Original: " + words);
// Nulls last, then natural order
words.sort(Comparator.nullsLast(String::compareTo));
System.out.println("Nulls last: " + words);
System.out.println("\nCustom comparator with nulls:");
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person(null, 25),
new Person("Charlie", 35),
new Person("Bob", null)
);
System.out.println("Original:");
people.forEach(System.out::println);
// Sort by name (nulls last)
people.sort(
Comparator.comparing(
Person::getName,
Comparator.nullsLast(String::compareTo)
)
);
System.out.println("\nName (nulls last):");
people.forEach(System.out::println);
System.out.println("\nMultiple null fields:");
List<Employee> employees = Arrays.asList(
new Employee("Alice", "Engineering", null),
new Employee(null, "Sales", 60000.0),
new Employee("Charlie", null, 80000.0),
new Employee("Bob", "Engineering", 70000.0)
);
// Sort by dept (nulls last), then name (nulls first)
employees.sort(
Comparator.comparing(
Employee::getDepartment,
Comparator.nullsLast(String::compareTo)
)
.thenComparing(
Employee::getName,
Comparator.nullsFirst(String::compareTo)
)
);
System.out.println("Dept (nulls last), name (nulls first):");
employees.forEach(System.out::println);
System.out.println("\nNull-safe chaining:");
List<Product> products = Arrays.asList(
new Product("Widget", null, 100),
new Product("Gadget", "A", null),
new Product(null, "B", 50),
new Product("Tool", "A", 200)
);
// Category nulls last, then quantity nulls first
products.sort(
Comparator.comparing(
Product::getCategory,
Comparator.nullsLast(String::compareTo)
)
.thenComparing(
Product::getQuantity,
Comparator.nullsFirst(Integer::compareTo)
)
);
System.out.println("Category/quantity null-safe:");
products.forEach(System.out::println);
System.out.println("\nAll nulls handling:");
List<Integer> numbers = Arrays.asList(5, null, 2, null, 8, 1, null);
System.out.println("Original: " + numbers);
// Nulls first
numbers.sort(Comparator.nullsFirst(Integer::compareTo));
System.out.println("Nulls first: " + numbers);
// Nulls last
numbers.sort(Comparator.nullsLast(Integer::compareTo));
System.out.println("Nulls last: " + numbers);
System.out.println("\nNatural order with nulls:");
List<Double> values = Arrays.asList(3.14, null, 2.71, 1.41, null);
System.out.println("Original: " + values);
// Natural order, nulls first
values.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("Natural, nulls first: " + values);
System.out.println("\nReversed with nulls:");
List<String> items = Arrays.asList("Zebra", null, "Apple", "Mango", null);
System.out.println("Original: " + items);
// Nulls first, descending
items.sort(
Comparator.nullsFirst(Comparator.<String>naturalOrder().reversed())
);
System.out.println("Nulls first, desc: " + items);
// Nulls last, descending
items.sort(
Comparator.nullsLast(Comparator.<String>naturalOrder().reversed())
);
System.out.println("Nulls last, desc: " + items);
System.out.println("\nCustom null object handling:");
List<Task> tasks = Arrays.asList(
new Task("Fix bug", 1),
null,
new Task("Write docs", 3),
null,
new Task("Review", 2)
);
System.out.println("Original:");
tasks.forEach(t -> System.out.println(" " + t));
// Handle entire null objects
tasks.sort(Comparator.nullsLast(
Comparator.comparing(Task::getPriority)
));
System.out.println("\nNull objects last:");
tasks.forEach(t -> System.out.println(" " + t));
}
static class Person {
private String name;
private Integer age;
Person(String name, Integer age) {
this.name = name;
this.age = age;
}
String getName() { return name; }
Integer getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
static class Employee {
private String name;
private String department;
private Double salary;
Employee(String name, String department, Double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
String getName() { return name; }
String getDepartment() { return department; }
Double getSalary() { return salary; }
@Override
public String toString() {
return String.format("%s (%s, $%.0f)",
name, department, salary == null ? 0.0 : salary);
}
}
static class Product {
private String name;
private String category;
private Integer quantity;
Product(String name, String category, Integer quantity) {
this.name = name;
this.category = category;
this.quantity = quantity;
}
String getCategory() { return category; }
Integer getQuantity() { return quantity; }
@Override
public String toString() {
return String.format("%s [%s, qty=%d]",
name, category, quantity == null ? 0 : quantity);
}
}
static class Task {
private String title;
private int priority;
Task(String title, int priority) {
this.title = title;
this.priority = priority;
}
int getPriority() { return priority; }
@Override
public String toString() {
return String.format("%s (priority=%d)", title, priority);
}
}
}
@seealso collections_util
Null handling
Use Comparator.nullsFirst() or nullsLast() to specify where nulls should appear in sorted order.
Exercise: Practical.java
Sort employees by department then salary, handling null departments