The Comparator<T> @FunctionalInterface interface in Java is part of the java.util package and is used to define custom sorting logic for objects. Unlike Comparable<T>, which imposes a natural ordering within a class, Comparator<T> allows sorting logic to be defined externally.
Key Features of Comparator
Enables custom sorting for objects.
Can be used to sort objects based on multiple fields.
Provides various default methods (since Java 8) for enhanced functionality.
Supports method references and lambda expressions for concise syntax.
Abstract Methods
int compare(T o1, T o2)
This is the only abstract method in Comparator. It must be implemented to define custom comparison logic.
Compares two objects and returns:
- Negative if o1 < o2
- Zero if o1 == o2
- Positive if o1 > o2
import java.util.*;
public class ComparatorExample {
public static void main(String[] args) {
Comparator<Integer> intComparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2; // Ascending order comparison
}
};
List<Integer> numbers = Arrays.asList(5, 2, 8, 1);
Collections.sort(numbers, intComparator);
// Output: [1, 2, 5, 8]
System.out.println(numbers);
}
}
Default Methods
Method
Description
Comparator<T> reversed();
Returns a comparator that reverses the order of comparison.
Comparator<T> thenComparing(Comparator<? super T> other);
Allows chaining multiple comparison criteria.
<U> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator);
Sorts using a key extracted from the object.
<U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T, ? extends U> keyExtractor);
Sorts using a Comparable key extracted from the object.
Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor);
Sorts based on an int key.
Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor);
Sorts based on a long key.
Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor);
Sorts based on a double key.
import java.util.*;
import java.util.function.*;
class Employee {
String name;
int age;
double salary;
Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return name + " (Age: " + age + ", Salary: " + salary + ")";
}
}
public class ComparatorExample {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", 30, 55000),
new Employee("Bob", 25, 50000),
new Employee("Charlie", 30, 60000),
new Employee("Dave", 25, 50000)
);
// Comparator by age
Comparator<Employee> byAge = Comparator.comparingInt(emp -> emp.age);
// reversed() - Reverse sorting order
employees.sort(byAge.reversed());
System.out.println("Sorted by Age (Descending): " + employees);
// Output: [Alice (Age: 30, Salary: 55000.0), Charlie (Age: 30, Salary: 60000.0), Bob (Age: 25, Salary: 50000.0), Dave (Age: 25, Salary: 50000.0)]
// thenComparing(Comparator) - First by age, then by salary
employees.sort(byAge.thenComparing(Comparator.comparingDouble(emp -> emp.salary)));
System.out.println("Sorted by Age, then Salary: " + employees);
// Output: [Bob (Age: 25, Salary: 50000.0), Dave (Age: 25, Salary: 50000.0), Alice (Age: 30, Salary: 55000.0), Charlie (Age: 30, Salary: 60000.0)]
// thenComparing(Function, Comparator) - First by age, then by name (custom comparator)
employees.sort(byAge.thenComparing(emp -> emp.name, Comparator.naturalOrder()));
System.out.println("Sorted by Age, then Name: " + employees);
// Output: [Bob (Age: 25, Salary: 50000.0), Dave (Age: 25, Salary: 50000.0), Alice (Age: 30, Salary: 55000.0), Charlie (Age: 30, Salary: 60000.0)]
// thenComparing(Function) - First by salary, then by name (natural ordering)
employees.sort(Comparator.comparingDouble(emp -> emp.salary).thenComparing(emp -> emp.name));
System.out.println("Sorted by Salary, then Name: " + employees);
// Output: [Bob (Age: 25, Salary: 50000.0), Dave (Age: 25, Salary: 50000.0), Alice (Age: 30, Salary: 55000.0), Charlie (Age: 30, Salary: 60000.0)]
// thenComparingInt(ToIntFunction) - First by salary, then by age
employees.sort(Comparator.comparingDouble(emp -> emp.salary).thenComparingInt(emp -> emp.age));
System.out.println("Sorted by Salary, then Age: " + employees);
// Output: [Bob (Age: 25, Salary: 50000.0), Dave (Age: 25, Salary: 50000.0), Alice (Age: 30, Salary: 55000.0), Charlie (Age: 30, Salary: 60000.0)]
// thenComparingLong(ToLongFunction) - (Assume a long field like employee ID, but here using age)
employees.sort(Comparator.comparing(emp -> emp.name).thenComparingLong(emp -> emp.age));
System.out.println("Sorted by Name, then Age: " + employees);
// Output: [Alice (Age: 30, Salary: 55000.0), Bob (Age: 25, Salary: 50000.0), Charlie (Age: 30, Salary: 60000.0), Dave (Age: 25, Salary: 50000.0)]
// thenComparingDouble(ToDoubleFunction) - First by age, then by salary
employees.sort(Comparator.comparingInt(emp -> emp.age).thenComparingDouble(emp -> emp.salary));
System.out.println("Sorted by Age, then Salary (double): " + employees);
// Output: [Bob (Age: 25, Salary: 50000.0), Dave (Age: 25, Salary: 50000.0), Alice (Age: 30, Salary: 55000.0), Charlie (Age: 30, Salary: 60000.0)]
}
}
Static Methods
Java 8 introduced several static methods for creating comparators.
Method
Description
Comparator<T> naturalOrder();
Returns a comparator based on natural ordering (assumes T implements Comparable<T>).
Comparator<T> reverseOrder();
Returns a comparator that reverses natural ordering.
<T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor);
Compares using a key extracted from the object.
<T, U> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator);
Compares using a key with a custom comparator.
<T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor);
Compares objects using an integer key.
<T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor);
Compares objects using a long key.
<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor);
Compares objects using a double key.
import java.util.*;
import java.util.function.*;
class Employee {
String name;
int age;
double salary;
Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return name + " (" + age + " yrs, $" + salary + ")";
}
}
public class ComparatorExample {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", 30, 60000.50),
new Employee("Bob", 25, 50000.75),
new Employee("Charlie", 35, 70000.25),
new Employee("Dave", 28, 45000.00)
);
// 1. Natural Order (Assumes T implements Comparable)
employees.sort(Comparator.comparing(Employee::name));
System.out.println("Natural Order (by name): " + employees);
// Output: [Alice (30 yrs, $60000.5), Bob (25 yrs, $50000.75), Charlie (35 yrs, $70000.25), Dave (28 yrs, $45000.0)]
// 2. Reverse Order
employees.sort(Comparator.comparing(Employee::name).reversed());
System.out.println("Reverse Order (by name): " + employees);
// Output: [Dave (28 yrs, $45000.0), Charlie (35 yrs, $70000.25), Bob (25 yrs, $50000.75), Alice (30 yrs, $60000.5)]
// 3. Comparing by Extracted Key (by age)
employees.sort(Comparator.comparing(Employee::age));
System.out.println("Sorted by Age: " + employees);
// Output: [Bob (25 yrs, $50000.75), Dave (28 yrs, $45000.0), Alice (30 yrs, $60000.5), Charlie (35 yrs, $70000.25)]
// 4. Comparing by Extracted Key with Custom Comparator (by name in reverse)
employees.sort(Comparator.comparing(Employee::name, Comparator.reverseOrder()));
System.out.println("Sorted by Name (reverse): " + employees);
// Output: [Dave (28 yrs, $45000.0), Charlie (35 yrs, $70000.25), Bob (25 yrs, $50000.75), Alice (30 yrs, $60000.5)]
// 5. Comparing using an Integer Key (by age)
employees.sort(Comparator.comparingInt(Employee::age));
System.out.println("Sorted by Age (int comparator): " + employees);
// Output: [Bob (25 yrs, $50000.75), Dave (28 yrs, $45000.0), Alice (30 yrs, $60000.5), Charlie (35 yrs, $70000.25)]
// 6. Comparing using a Long Key (Assume we have long ID field, here we use age for demo)
employees.sort(Comparator.comparingLong(Employee::age));
System.out.println("Sorted by Age (long comparator): " + employees);
// Output: [Bob (25 yrs, $50000.75), Dave (28 yrs, $45000.0), Alice (30 yrs, $60000.5), Charlie (35 yrs, $70000.25)]
// 7. Comparing using a Double Key (by salary)
employees.sort(Comparator.comparingDouble(Employee::salary));
System.out.println("Sorted by Salary (double comparator): " + employees);
// Output: [Dave (28 yrs, $45000.0), Bob (25 yrs, $50000.75), Alice (30 yrs, $60000.5), Charlie (35 yrs, $70000.25)]
}
}
Utility Methods in Map.Entry
The Map.Entry class provides useful comparators for sorting maps.
Method
Description
static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K, V>> comparingByValue();
Returns a comparator that compares Map.Entry objects by value.
static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp);
Compares Map.Entry objects by value using a custom comparator.
static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K, V>> comparingByKey();
Compares Map.Entry objects by key.
static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp);
Compares Map.Entry objects by key using a custom comparator.
import java.util.*;
import java.util.Map.Entry;
public class ComparatorMapExample {
public static void main(String[] args) {
Map<String, Integer> salaryMap = new HashMap<>();
salaryMap.put("Alice", 60000);
salaryMap.put("Bob", 50000);
salaryMap.put("Charlie", 70000);
salaryMap.put("Dave", 45000);
List<Entry<String, Integer>> entryList = new ArrayList<>(salaryMap.entrySet());
// 1. Comparing by Value (Natural Order)
entryList.sort(Entry.comparingByValue());
System.out.println("Sorted by Value (ascending): " + entryList);
// Output: [Dave=45000, Bob=50000, Alice=60000, Charlie=70000]
// 2. Comparing by Value (Descending Order)
entryList.sort(Entry.comparingByValue(Comparator.reverseOrder()));
System.out.println("Sorted by Value (descending): " + entryList);
// Output: [Charlie=70000, Alice=60000, Bob=50000, Dave=45000]
// 3. Comparing by Key (Natural Order)
entryList.sort(Entry.comparingByKey());
System.out.println("Sorted by Key (ascending): " + entryList);
// Output: [Alice=60000, Bob=50000, Charlie=70000, Dave=45000]
// 4. Comparing by Key (Descending Order)
entryList.sort(Entry.comparingByKey(Comparator.reverseOrder()));
System.out.println("Sorted by Key (descending): " + entryList);
// Output: [Dave=45000, Charlie=70000, Bob=50000, Alice=60000]
}
}