Introduction
Comparator interface is used to define custom sorting order for objects. It provides flexibility to sort objects in different ways.
Comparable vs Comparator
| Feature | Comparable | Comparator |
|---|---|---|
| Package | java.lang | java.util |
| Method | compareTo(T o) | compare(T o1, T o2) |
| Sorting logic | Inside class | External class |
| Modification | Modify original class | Separate class |
| Multiple sorts | One way only | Multiple ways |
| Usage | Collections.sort(list) | Collections.sort(list, comparator) |
Basic Syntax
import java.util.Comparator;
class MyComparator implements Comparator<Type> {
@Override
public int compare(Type o1, Type o2) {
// Return negative if o1 < o2
// Return 0 if o1 == o2
// Return positive if o1 > o2
}
}
Simple Example
import java.util.*;
class Student {
String name;
int marks;
Student(String name, int marks) {
this.name = name;
this.marks = marks;
}
@Override
public String toString() {
return name + ": " + marks;
}
}
// Comparator for sorting by marks
class MarksComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.marks - s2.marks; // Ascending order
}
}
// Comparator for sorting by name
class NameComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.name.compareTo(s2.name); // Alphabetical
}
}
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("John", 85));
students.add(new Student("Alice", 92));
students.add(new Student("Bob", 78));
// Sort by marks
Collections.sort(students, new MarksComparator());
System.out.println("By marks: " + students);
// Sort by name
Collections.sort(students, new NameComparator());
System.out.println("By name: " + students);
}
}
Return Value Rules
compare(o1, o2) returns:
• Negative number (e.g., -1): o1 comes before o2 (o1 < o2)
• Zero (0): o1 equals o2 (o1 == o2)
• Positive number (e.g., +1): o1 comes after o2 (o1 > o2)
Ascending vs Descending
import java.util.*;
class AscendingComparator implements Comparator<Integer> {
@Override
public int compare(Integer a, Integer b) {
return a - b; // Ascending: smaller first
}
}
class DescendingComparator implements Comparator<Integer> {
@Override
public int compare(Integer a, Integer b) {
return b - a; // Descending: larger first
}
}
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
Collections.sort(numbers, new AscendingComparator());
System.out.println("Ascending: " + numbers); // [1, 2, 5, 8, 9]
Collections.sort(numbers, new DescendingComparator());
System.out.println("Descending: " + numbers); // [9, 8, 5, 2, 1]
}
}
Lambda Expression (Java 8+)
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
// Ascending using lambda
Collections.sort(numbers, (a, b) -> a - b);
System.out.println("Ascending: " + numbers);
// Descending using lambda
Collections.sort(numbers, (a, b) -> b - a);
System.out.println("Descending: " + numbers);
}
}
Multiple Sorting Criteria
import java.util.*;
class Employee {
String name;
int salary;
int age;
Employee(String name, int salary, int age) {
this.name = name;
this.salary = salary;
this.age = age;
}
@Override
public String toString() {
return name + " (" + salary + ", " + age + ")";
}
}
// Sort by salary, then by age if salary is same
class EmployeeComparator implements Comparator<Employee> {
@Override
public int compare(Employee e1, Employee e2) {
// First compare by salary
int salaryCompare = e1.salary - e2.salary;
if (salaryCompare != 0) {
return salaryCompare;
}
// If salary same, compare by age
return e1.age - e2.age;
}
}
public class Main {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("John", 50000, 30));
employees.add(new Employee("Alice", 60000, 25));
employees.add(new Employee("Bob", 50000, 28));
Collections.sort(employees, new EmployeeComparator());
System.out.println(employees);
// Bob (50000, 28)
// John (50000, 30)
// Alice (60000, 25)
}
}
String Comparison
import java.util.*;
class Student {
String name;
Student(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
// Case-sensitive sorting
class CaseSensitiveComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.name.compareTo(s2.name);
}
}
// Case-insensitive sorting
class CaseInsensitiveComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.name.compareToIgnoreCase(s2.name);
}
}
public class Main {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("alice"),
new Student("Bob"),
new Student("Charlie")
);
Collections.sort(students, new CaseSensitiveComparator());
System.out.println("Case-sensitive: " + students);
// [Bob, Charlie, alice]
Collections.sort(students, new CaseInsensitiveComparator());
System.out.println("Case-insensitive: " + students);
// [alice, Bob, Charlie]
}
}
Comparator.comparing() (Java 8+)
import java.util.*;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
String getName() { return name; }
int getAge() { return age; }
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class Main {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("John", 30),
new Person("Alice", 25),
new Person("Bob", 35)
);
// Sort by name
people.sort(Comparator.comparing(Person::getName));
System.out.println("By name: " + people);
// Sort by age
people.sort(Comparator.comparing(Person::getAge));
System.out.println("By age: " + people);
// Sort by age descending
people.sort(Comparator.comparing(Person::getAge).reversed());
System.out.println("By age (desc): " + people);
}
}
Chaining Comparators
import java.util.*;
class Product {
String name;
double price;
int rating;
Product(String name, double price, int rating) {
this.name = name;
this.price = price;
this.rating = rating;
}
@Override
public String toString() {
return name + " ($" + price + ", " + rating + "★)";
}
}
public class Main {
public static void main(String[] args) {
List<Product> products = Arrays.asList(
new Product("Laptop", 800, 4),
new Product("Phone", 500, 5),
new Product("Tablet", 500, 4)
);
// Sort by price, then by rating
products.sort(
Comparator.comparing((Product p) -> p.price)
.thenComparing(p -> p.rating)
);
System.out.println(products);
// Tablet ($500.0, 4★)
// Phone ($500.0, 5★)
// Laptop ($800.0, 4★)
}
}
Real-World Example: Employee Management
import java.util.*;
class Employee {
int id;
String name;
String department;
double salary;
Employee(int id, String name, String department, double salary) {
this.id = id;
this.name = name;
this.department = department;
this.salary = salary;
}
@Override
public String toString() {
return id + ". " + name + " (" + department + ", $" + salary + ")";
}
}
class EmployeeComparators {
// Sort by ID
static class ByID implements Comparator<Employee> {
public int compare(Employee e1, Employee e2) {
return e1.id - e2.id;
}
}
// Sort by name
static class ByName implements Comparator<Employee> {
public int compare(Employee e1, Employee e2) {
return e1.name.compareTo(e2.name);
}
}
// Sort by department, then salary
static class ByDepartmentAndSalary implements Comparator<Employee> {
public int compare(Employee e1, Employee e2) {
int deptCompare = e1.department.compareTo(e2.department);
if (deptCompare != 0) {
return deptCompare;
}
return Double.compare(e2.salary, e1.salary); // Descending salary
}
}
// Sort by salary (descending)
static class BySalaryDesc implements Comparator<Employee> {
public int compare(Employee e1, Employee e2) {
return Double.compare(e2.salary, e1.salary);
}
}
}
public class Main {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(3, "John", "IT", 60000));
employees.add(new Employee(1, "Alice", "HR", 50000));
employees.add(new Employee(2, "Bob", "IT", 65000));
employees.add(new Employee(4, "Carol", "HR", 55000));
System.out.println("Original:");
employees.forEach(System.out::println);
System.out.println("\nBy ID:");
employees.sort(new EmployeeComparators.ByID());
employees.forEach(System.out::println);
System.out.println("\nBy Name:");
employees.sort(new EmployeeComparators.ByName());
employees.forEach(System.out::println);
System.out.println("\nBy Salary (Highest first):");
employees.sort(new EmployeeComparators.BySalaryDesc());
employees.forEach(System.out::println);
System.out.println("\nBy Department, then Salary:");
employees.sort(new EmployeeComparators.ByDepartmentAndSalary());
employees.forEach(System.out::println);
}
}
Null Handling
import java.util.*;
class NullSafeComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
// Handle nulls
if (s1 == null && s2 == null) return 0;
if (s1 == null) return -1; // null comes first
if (s2 == null) return 1;
return s1.compareTo(s2);
}
}
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", null, "Alice", null, "Bob");
names.sort(new NullSafeComparator());
System.out.println(names); // [null, null, Alice, Bob, John]
// Java 8+ nullsFirst/nullsLast
names.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println(names); // [Alice, Bob, John, null, null]
}
}
Anonymous Inner Class
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
// Anonymous comparator
Collections.sort(numbers, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return b - a; // Descending
}
});
System.out.println(numbers); // [9, 8, 5, 2, 1]
}
}
TreeSet with Comparator
import java.util.*;
class Student {
String name;
int marks;
Student(String name, int marks) {
this.name = name;
this.marks = marks;
}
@Override
public String toString() {
return name + ": " + marks;
}
}
public class Main {
public static void main(String[] args) {
// TreeSet with custom comparator (sorted by marks)
Set<Student> students = new TreeSet<>(
(s1, s2) -> s1.marks - s2.marks
);
students.add(new Student("John", 85));
students.add(new Student("Alice", 92));
students.add(new Student("Bob", 78));
System.out.println(students);
// [Bob: 78, John: 85, Alice: 92]
}
}
Common Patterns
1. Reverse Order:
Collections.sort(list, Collections.reverseOrder());
// Or
Collections.sort(list, Comparator.reverseOrder());
2. Natural Order:
Collections.sort(list, Comparator.naturalOrder());
3. Reversed:
Comparator<String> comp = Comparator.naturalOrder();
list.sort(comp.reversed());
Quick Reference
import java.util.Comparator;
// Basic implementation
class MyComparator implements Comparator<Type> {
public int compare(Type o1, Type o2) {
return o1.field - o2.field; // Ascending
// return o2.field - o1.field; // Descending
}
}
// Usage
Collections.sort(list, new MyComparator());
// Lambda (Java 8+)
list.sort((a, b) -> a.field - b.field);
// Method reference
list.sort(Comparator.comparing(Type::getField));
// Multiple criteria
list.sort(Comparator.comparing(Type::getField1)
.thenComparing(Type::getField2));
// Reverse
list.sort(Comparator.comparing(Type::getField).reversed());
Common Mistakes
// ✗ Wrong - may cause overflow
return o1.largeNumber - o2.largeNumber;
// ✓ Correct - use compare methods
return Integer.compare(o1.number, o2.number);
return Double.compare(o1.value, o2.value);
return Long.compare(o1.longValue, o2.longValue);
// ✗ Wrong - null pointer exception
return o1.name.compareTo(o2.name);
// ✓ Correct - handle nulls
if (o1.name == null) return -1;
if (o2.name == null) return 1;
return o1.name.compareTo(o2.name);
Exam Tips
Remember:
- Comparator for custom sorting
- compare(o1, o2) method
- Negative: o1 < o2 (o1 first)
- Zero: o1 == o2 (equal)
- Positive: o1 > o2 (o2 first)
- Ascending: return a - b
- Descending: return b - a
- Lambda: (a, b) -> expression
- comparing(): Comparator.comparing()
- Multiple criteria with thenComparing()
Common Questions:
- What is Comparator interface?
- Comparable vs Comparator?
- How to sort in descending order?
- Return values of compare()?
- Lambda with Comparator?
- Multiple sorting criteria?
- TreeSet with Comparator?
- Null handling in Comparator?
- Comparator.comparing() usage?
- When to use Comparator?