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.