Last Updated on September 18, 2024

The Java Comparator interface is a tool in the Java Collections Framework that allows developers to define custom ordering for objects. It provides a way to compare two objects to determine their order, separate from the objects’ natural ordering (if any). This separation of comparison logic from the object itself adheres to the Single Responsibility Principle and allows for multiple ways to order the same set of objects.

In this comprehensive guide, we’ll explore the Java Comparator interface in depth. We’ll cover its purpose, main methods, implementation techniques, and practical applications.

Overview

The Comparator interface is part of the java.util package and has been a core part of Java since version 1.2. Its primary purpose is to define a comparison method which imposes a total ordering on some collection of objects.

Comparators can be passed to sorting methods (such as Collections.sort() or Arrays.sort()) to provide a specific ordering.

Here’s the basic definition of the Comparator interface:

public interface Comparator<T> {
    int compare(T o1, T o2);
}

By implementing the interface, we can define multiple comparators for the same class, each providing a different ordering. This allows for great flexibility in how objects are sorted in different contexts.

Comparators can be composed together to create more complex sorting criteria. Java 8 introduced several default methods in the Comparator interface to facilitate this.

Unlike the Comparable interface, which provides a natural ordering for a class, Comparator allows for comparison logic to be defined outside of the class being compared. This is particularly useful when you want to sort objects that you can’t modify or when you need multiple ways to sort the same objects.

Main Methods

The Comparator interface has evolved significantly since its introduction. While the core compare() method remains the heart of the interface, Java 8 introduced several default and static methods that enhance its functionality and ease of use.

compare

This is the primary method of the Comparator interface. It compares two objects and returns an integer value

Example implementation:

Comparator<String> lengthComparator = new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
};

reversed

Introduced in Java 8, this default method returns a comparator that imposes the reverse ordering of the original comparator.

Comparator<String> reversedLengthComparator = lengthComparator.reversed();

thenComparing(Comparator other)

This method allows chaining of comparators.

It returns a lexicographic-order comparator with the original as the primary comparator and the other as the secondary.

Comparator<String> lengthThenAlphabetical = lengthComparator
    .thenComparing(Comparator.naturalOrder());

comparing(Function keyExtractor)

A static method that returns a comparator that compares by extracting a key and using the key’s natural order.

Comparator<Person> compareByAge = Comparator.comparing(Person::getAge);

reverseOrder() / naturalOrder()

These static methods return comparators that impose the reverse of the natural ordering and the natural ordering, respectively.

Comparator<String> reverseAlphabetical = Comparator.reverseOrder();
Comparator<String> alphabetical = Comparator.naturalOrder();

Usage

Let’s dive into how to implement custom comparators for your own classes. We’ll use a Book class as an example and create various comparators for it.

import java.time.LocalDate;

public class Book {
    private String title;
    private String author;
    private String isbn;
    private LocalDate publicationDate;
    private int pageCount;
    private double rating;

    // Constructor
    public Book(String title, String author, String isbn, LocalDate publicationDate, int pageCount, double rating) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
        this.publicationDate = publicationDate;
        this.pageCount = pageCount;
        this.rating = rating;
    }

    // Getters
    public String getTitle() { return title; }
    public String getAuthor() { return author; }
    public String getIsbn() { return isbn; }
    public LocalDate getPublicationDate() { return publicationDate; }
    public int getPageCount() { return pageCount; }
    public double getRating() { return rating; }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", isbn='" + isbn + '\'' +
                ", publicationDate=" + publicationDate +
                ", pageCount=" + pageCount +
                ", rating=" + rating +
                '}';
    }
}

Variety of comparators for our Book class

import java.util.Comparator;

public class BookComparators {

    // Compare by title
    public static final Comparator<Book> BY_TITLE = 
        Comparator.comparing(Book::getTitle);

    // Compare by author, then title
    public static final Comparator<Book> BY_AUTHOR_THEN_TITLE = 
        Comparator.comparing(Book::getAuthor)
                  .thenComparing(Book::getTitle);

    // Compare by publication date (most recent first)
    public static final Comparator<Book> BY_PUBLICATION_DATE = 
        Comparator.comparing(Book::getPublicationDate).reversed();

    // Compare by rating (highest first), then title
    public static final Comparator<Book> BY_RATING_THEN_TITLE = 
        Comparator.comparingDouble(Book::getRating).reversed()
                  .thenComparing(Book::getTitle);

    // Compare by page count (shortest first)
    public static final Comparator<Book> BY_PAGE_COUNT = 
        Comparator.comparingInt(Book::getPageCount);

    // Custom comparator: by "value for money" (rating / page count, highest first)
    public static final Comparator<Book> BY_VALUE_FOR_MONEY = 
        Comparator.comparingDouble((Book b) -> b.getRating() / b.getPageCount()).reversed();

    // Null-safe comparator by ISBN
    public static final Comparator<Book> NULL_SAFE_BY_ISBN = 
        Comparator.nullsLast(Comparator.comparing(Book::getIsbn));
}

Main method to sort a list of books

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

public class LibraryCatalog {
    public static void main(String[] args) {
        List<Book> books = new ArrayList<>();
        books.add(new Book("The Great Gatsby", "F. Scott Fitzgerald", "9780743273565", LocalDate.of(1925, 4, 10), 180, 4.2));
        books.add(new Book("To Kill a Mockingbird", "Harper Lee", "9780446310789", LocalDate.of(1960, 7, 11), 281, 4.27));
        books.add(new Book("1984", "George Orwell", "9780451524935", LocalDate.of(1949, 6, 8), 328, 4.18));
        books.add(new Book("Pride and Prejudice", "Jane Austen", "9780141439518", LocalDate.of(1813, 1, 28), 432, 4.25));
        books.add(new Book("The Catcher in the Rye", "J.D. Salinger", "9780316769174", LocalDate.of(1951, 7, 16), 234, 3.8));

        System.out.println("Original list:");
        books.forEach(System.out::println);

        System.out.println("\nSorted by title:");
        books.sort(BookComparators.BY_TITLE);
        books.forEach(System.out::println);

        System.out.println("\nSorted by author, then title:");
        books.sort(BookComparators.BY_AUTHOR_THEN_TITLE);
        books.forEach(System.out::println);

        System.out.println("\nSorted by publication date (most recent first):");
        books.sort(BookComparators.BY_PUBLICATION_DATE);
        books.forEach(System.out::println);

        System.out.println("\nSorted by rating (highest first), then title:");
        books.sort(BookComparators.BY_RATING_THEN_TITLE);
        books.forEach(System.out::println);

        System.out.println("\nSorted by page count (shortest first):");
        books.sort(BookComparators.BY_PAGE_COUNT);
        books.forEach(System.out::println);

        System.out.println("\nSorted by value for money (rating / page count, highest first):");
        books.sort(BookComparators.BY_VALUE_FOR_MONEY);
        books.forEach(System.out::println);
    }
}

Conclusion

The Comparator interface exemplifies Java’s commitment to providing powerful, flexible, and type-safe tools for developers.

Whether we’re working on a simple sorting task or building a complex data processing system, mastering the Comparator interface will undoubtedly enhance your Java programming toolkit.

Comparator interface is not just about sorting – it’s about defining relationships between objects in a way that’s clear, reusable, and separate from the objects themselves.

This separation of concerns aligns well with good object-oriented design principles and can lead to more modular and maintainable code.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top