In Java, sorting collections of objects is a common task that often requires custom sorting criteria. The Comparator
interface plays a crucial role in this process, allowing developers to define their own order for sorting objects.
The Comparator
interface is part of the java.util
package and is used to compare two objects for order. It provides a means to define custom sorting logic that can be used with various collection classes such as lists, sets, and maps. Unlike the Comparable
interface, which requires the class to implement a natural ordering, Comparator
allows you to create multiple ways to compare objects of the same class.
Comparator
is a functional interface, meaning it can be used with lambda expressions.The Comparator
interface includes several methods, but the most important ones are:
int compare(T o1, T o2)
: Compares its two arguments for order. Returns a negative integer, zero, or a positive integer if the first argument is less than, equal to, or greater than the second.boolean equals(Object obj)
: Indicates whether some other object is “equal to” this comparator.Let’s create a simple example to illustrate the use of a Comparators
to sort a list of integers in reverse order.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ComparatorExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(4);
numbers.add(3);
numbers.add(2);
numbers.add(5);
// Create a comparator for reverse order
Comparator<Integer> reverseComparator = (o1, o2) -> o2 - o1;
// Sort using the comparator
Collections.sort(numbers, reverseComparator);
// Display the sorted list
System.out.println("Sorted in reverse order: " + numbers);
}
}
//Output:
Sorted in reverse order: [5, 4, 3, 2, 1]
In this example, we create a Comparator
that sorts integers in reverse order and use it to sort a list.
You can implement custom comparators by creating a class that implements the Comparator
interface. This allows you to define multiple comparison strategies for the same object type.
Let’s define a Employee
class and create a comparator to sort employees by their names and another to sort them by their ages.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Employee {
private String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Employee{" + "name='" + name + '\'' + ", age=" + age + '}';
}
}
public class EmployeeComparatorExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 30));
employees.add(new Employee("Bob", 25));
employees.add(new Employee("Charlie", 35));
// Sort by name
Collections.sort(employees, new Comparator<Employee>() {
@Override
public int compare(Employee e1, Employee e2) {
return e1.getName().compareTo(e2.getName());
}
});
System.out.println("Employees sorted by name: " + employees);
// Sort by age
Collections.sort(employees, new Comparator<Employee>() {
@Override
public int compare(Employee e1, Employee e2) {
return Integer.compare(e1.getAge(), e2.getAge());
}
});
System.out.println("Employees sorted by age: " + employees);
}
}
//Output:
Employees sorted by name: [Employee{name='Alice', age=30},
Employee{name='Bob', age=25},
Employee{name='Charlie', age=35}]
Employees sorted by age: [Employee{name='Bob', age=25},
Employee{name='Alice', age=30},
Employee{name='Charlie', age=35}]
In this example, we first sort the list of employees by name and then by age using different comparators.
Since Java 8, you can use lambda expressions to simplify the creation of comparators. This makes the code cleaner and more readable.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class LambdaComparatorExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 30));
employees.add(new Employee("Bob", 25));
employees.add(new Employee("Charlie", 35));
// Sort by name using a lambda expression
Collections.sort(employees, (e1, e2) -> e1.getName().compareTo(e2.getName()));
System.out.println("Employees sorted by name (lambda): " + employees);
// Sort by age using a lambda expression
Collections.sort(employees, (e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
System.out.println("Employees sorted by age (lambda): " + employees);
}
}
//Output:
Employees sorted by name (lambda): [Employee{name='Alice', age=30},
Employee{name='Bob', age=25},
Employee{name='Charlie', age=35}]
Employees sorted by age (lambda):
[Employee{name='Bob', age=25}, Employee{name='Alice', age=30},
Employee{name='Charlie', age=35}]
Java 8 introduced several static methods in the Comparator
interface that make it easier to create common comparators. Some of the most useful static methods include:
Comparator.naturalOrder()
: Returns a comparator that compares objects in their natural order.Comparator.reverseOrder()
: Returns a comparator that reverses the natural ordering.Comparator.comparing(Function<? super T, ? extends U> keyExtractor)
: Returns a comparator that compares by a specified key.Comparator.comparingInt(ToIntFunction<? super T> keyExtractor)
: Similar to comparing
, but for int
values.import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class StaticComparatorExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 30));
employees.add(new Employee("Bob", 25));
employees.add(new Employee("Charlie", 35));
// Sort by name using static comparator method
employees.sort(Comparator.comparing(Employee::getName));
System.out.println("Employees sorted by name (static method): " + employees);
// Sort by age using static comparator method
employees.sort(Comparator.comparingInt(Employee::getAge));
System.out.println("Employees sorted by age (static method): " + employees);
}
}
//Output:
Employees sorted by name (static method):
[Employee{name='Alice', age=30},
Employee{name='Bob', age=25}, Employee{name='Charlie', age=35}]
Employees sorted by age (static method):
[Employee{name='Bob', age=25},Employee{name='Alice', age=30},
Employee{name='Charlie', age=35}]
You can chain multiple comparators to sort by multiple criteria. This is particularly useful when you want to sort by one field and then by another field if the first field is equal.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ChainedComparatorExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 30));
employees.add(new Employee("Bob", 25));
employees.add(new Employee("Charlie", 25));
employees.add(new Employee("Alice", 25));
// Chained comparators: first by age, then by name
Comparator<Employee> byAge = Comparator.comparingInt(Employee::getAge);
Comparator<Employee> byName = Comparator.comparing(Employee::getName);
Comparator<Employee> combinedComparator = byAge.thenComparing(byName);
Collections.sort(employees, combinedComparator);
System.out.println("Employees sorted by age and name: " + employees);
}
}
//Output:
Employees sorted by age and name: [Employee{name='Alice',
age=25},Employee{name='Bob',age=25},
Employee{name='Alice', age=30},
Employee{name='Charlie', age=25}]
In this example, employees are sorted first by age and then by name if they have the same age.
The Comparator
interface is commonly used with various collection classes. Here are a few examples:
We’ve seen multiple examples of sorting lists using Comparator
, but here’s a summary:
List<String> names = Arrays.asList("John", "Alice", "Bob");
Collections.sort(names, Comparator.naturalOrder());
System.out.println("Sorted names: " + names);
When working with a TreeSet
, you can provide a comparator at the time of creation:
Set<Employee> employeeSet = new TreeSet<>(Comparator.comparing(Employee::getName));
employeeSet.add(new Employee("Charlie", 35));
employeeSet.add(new Employee("Alice", 30));
employeeSet.add(new Employee("Bob", 25));
System.out.println("Employees in TreeSet: " + employeeSet);
For sorting a Map
by its keys or values, you can use a Comparator
:
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 30);
map.put("Bob", 25);
map.put("Charlie", 35);
// Sorting by keys
Map<String, Integer> sortedByKey = new TreeMap<>(Comparator.naturalOrder());
sortedByKey.putAll(map);
System.out.println("Sorted map by keys: " + sortedByKey);
Let’s say you have an e-commerce application where you need to manage and sort a list of products based on various criteria such as price and name.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "Product{" + "name='" + name + '\'' + ", price=" + price + '}';
}
}
public class ProductSortingExample {
public static void main(String[] args) {
List<Product> products = new ArrayList<>();
products.add(new Product("Laptop", 999.99));
products.add(new Product("Smartphone", 499.99));
products.add(new Product("Tablet", 299.99));
// Sort by price
Collections.sort(products, Comparator.comparingDouble(Product::getPrice));
System.out.println("Products sorted by price: " + products);
// Sort by name
Collections.sort(products, Comparator.comparing(Product::getName));
System.out.println("Products sorted by name: " + products);
}
}
//Output:
Products sorted by price:
[Product{name='Tablet', price=299.99},
Product{name='Smartphone', price=499.99},
Product{name='Laptop', price=999.99}]
Products sorted by name:
[Product{name='Laptop', price=999.99},
Product{name='Smartphone',price=499.99},
Product{name='Tablet', price=299.99}]