Hello guys, the Comparator class is used to provide code or logic for comparing objects in Java, while sorting a list of objects or a collection of objects. It's close cousin of Comparable which provides natural order sorting e.g. ascending and descending orders for numbers like int, short, long or float, and lexicographic order for String i.e. the order on which words are arranged in dictionaries. The Comparators are used while sorting arrays, lists and collections. You pass logic to compare objects and sorting methods like Collections.sort() use that logic to compare elements until they are arranged in sorted order.
The old way of writing Comparator was by using anonymous class, as there was no other way to pass code to a method before Java 8. That was clumsy, but served the purpose well. Now that you have more easier way to pass code to a method e.g. Collections.sort() using lambda expression and method references.
JDK 8 has totally revamped the way you use Comparator in Java, not only you have lambdas and method reference to write on the fly Comparators in clear and concise way, but also you have got so many useful method for day to day comparisons e.g. comparing(), thenComparing(), and reverseOrder() method from Comparator class, which allows you to compare objects on any property, chaining multiple comparators to do comparison on multiple properties, and to perform reverse order Comparison.
You also got methods like Integer.compare(int x, int y) which allows you to compare two integers x and y. This method returns 0 if two integers are equal, positive if first integer is greater than second and negative otherwise.
So, you don't need to write something x-> y which can create subtle bugs because it can overflow for negative numbers and only works when both x and y is positive.
Sine new features of JDK 8, particularly lambdas, method reference and stream have completely changed the way you write code in Java, it make sense to revisit the way we write Comparators for sorting.
In this article, I'll show you 10 examples of implementing Comparator in Java to cover many day to day programming task e.g. sorting in custom order, sorting in reverse order, sorting with multiple conditions and the new way of chaining multiple Comparators in Java 8.
10 Examples of Comparator, Comparable, and Sorting in Java 8
In order to demonstrate these Java 8 Comparator example, we need a POJO, a plain old Java object e.g. a Book, an Employee, a Person, or an Order. I'll use a Book object with fields like title, author and price.
This will then gives us nice combination of sorting e.g. sorting list of books on title, which is String or price which we keep int for the sake of simplicity.
In Java, you can create a Book object that is sortable by implementing the Comparable interface or by using a Comparator to define the sorting criteria.
Here's an example of how you can create a Book class and make it sortable using the Comparable interface:
public class Book implements Comparable<Book> {
private String title;
private String author;
private int publicationYear;
public Book(String title, String author, int publicationYear) {
this.title = title;
this.author = author;
this.publicationYear = publicationYear;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public int getPublicationYear() {
return publicationYear;
}
@Override
public int compareTo(Book otherBook) {
// Compare books based on their publication year
return Integer.compare(this.publicationYear, otherBook.publicationYear);
}
@Override
public String toString() {
return title + " by " + author + " (" + publicationYear + ")";
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Demo{
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Effective Java", "Josh Bloch", 2000));
books.add(new Book("Clean Code", "Uncle Bob", 1995));
books.add(new Book("Refactoring", "Martin Flower", 2010));
// Sort the books based on publication year
Collections.sort(books);
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
1. Comparator without lambda expression in Java 7 (Anonymous Comparator)
There is no better way to understand a new concept then knowing how it used to be done before. So, before learning new way to write comparators in Java 8, let's say how it is done currently in Java 7 and before.
class BookComparator implements Comparator<Book> {
@Override
public int compare(Book book1, Book book2) {
// First, compare based on publication year
int yearComparison
= Integer.compare(book1.getPublicationYear(),
book2.getPublicationYear());
// If the publication years are the same, compare based on price
if (yearComparison == 0) {
return Double.compare(book1.getPrice(), book2.getPrice());
}
return yearComparison;
}
}
In this Java 7 style code, we've created a BookComparator class that implements the Comparator interface and provides the comparison logic for sorting Book objects based on publication year and price. We then use this custom Comparator when calling Collections.sort to sort the list of Book object
Suppose you have a list of Book object and you want to sort them on title. This can be done by using Collections.sort() method and passing a Comparator which compare Book objects on title, here is how you can write it using Anonymous class in Java 8.
// Sort the books based on publication year and title using an anonymous Comparator
Collections.sort(books, new Comparator<Book>() {
@Override
public int compare(Book book1, Book book2) {
// First, compare based on publication year
int yearComparison
= Integer.compare(book1.getPublicationYear(),
book2.getPublicationYear());
// If the publication years are the same, compare based on title
if (yearComparison == 0) {
return book1.getTitle().compareTo(book2.getTitle());
}
return yearComparison;
}
});
2. Comparator with lambda expression in Java 8
In Java 8, you can simplify the code further by using a lambda expression to create the Comparator for sorting Book objects based on both publication year and title. Here's how you can do it:
public class ComparatorDemo{
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books based on publication year and title using a lambda expression
Collections.sort(books, (book1, book2) -> {
// First, compare based on publication year
int yearComparison
= Integer.compare(book1.getPublicationYear(), book2.getPublicationYear());
// If the publication years are the same, compare based on title
if (yearComparison == 0) {
return book1.getTitle().compareTo(book2.getTitle());
}
return yearComparison;
});
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
In this code, we use a lambda expression to create a Comparator that defines the comparison logic for sorting Book objects. The lambda expression compares books first by publication year and then by title, just like in the previous example. This is a more concise and expressive way to create a Comparator in Java 8.
3) Sorting with type inference and lambdas in Java 8
In Java 8, you can sort a list of Book objects using type inference and lambda expressions with the Comparator interface. Here's how you can do it:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Book {
private String title;
private String author;
private int publicationYear;
private double price;
public Book(String title, String author, int publicationYear, double price) {
this.title = title;
this.author = author;
this.publicationYear = publicationYear;
this.price = price;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public int getPublicationYear() {
return publicationYear;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return title + " by " + author + " (" + publicationYear + "), Price: $" + price;
}
}
public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books by publication year using type inference and lambdas
books.sort(Comparator.comparing(Book::getPublicationYear));
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
4) Comparator with method reference in Java 8
You can also create a Comparator with method references in Java 8 to sort Book objects by price and then by title.public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books by price and then by title using method references
Comparator<Book> comparator = Comparator
.comparing(Book::getPrice)
.thenComparing(Book::getTitle);
Collections.sort(books, comparator);
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
In this code, we use method references Book::getPrice and Book::getTitle within the Comparator.comparing and thenComparing methods, respectively, to create a Comparator that sorts Book objects by price and then by title.
5) Comparator using static method reference in Java 8
In Java 8, you can create a Comparator using a static method reference to sort Book objects. Here's an example:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Book {
private String title;
private String author;
private int publicationYear;
private double price;
public Book(String title, String author, int publicationYear, double price) {
this.title = title;
this.author = author;
this.publicationYear = publicationYear;
this.price = price;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public int getPublicationYear() {
return publicationYear;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return title + " by " + author + " (" + publicationYear + "), Price: $" + price;
}
// Static method to compare books by price
public static int compareByPrice(Book book1, Book book2) {
return Double.compare(book1.getPrice(), book2.getPrice());
}
// Static method to compare books by title
public static int compareByTitle(Book book1, Book book2) {
return book1.getTitle().compareTo(book2.getTitle());
}
}
public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books by price and then by title using static method references
Comparator<Book> priceComparator = Book::compareByPrice;
Comparator<Book> titleComparator = Book::compareByTitle;
Collections.sort(books, priceComparator.thenComparing(titleComparator));
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
6. Sorting using Comparator.comparing() method
In Java 8, you can further simplify the sorting code by relying on type inference and lambda expressions. You can use the Comparator.comparing() method and method chaining to specify multiple sorting criteria.Here's how you can do it:
public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books based on publication year
// and title using type inference and lambdas
Collections.sort(books, Comparator
.comparing(Book::getPublicationYear)
.thenComparing(Book::getTitle));
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
In this code, we use the Comparator.comparing() method along with method chaining to specify the sorting criteria.
We first sort by publication year using Comparator.comparing(Book::getPublicationYear), and then we use thenComparing(Book::getTitle) to sort by title in case the publication years are the same. This approach leverages type inference and lambda expressions to make the code concise and readable.
7. Sorting in reverse order using Comparator.reverseOrder()
You can sort a list of objects in reverse order using the Comparator.reverseOrder() method in Java.
Here's how you can sort a list of Book objects in reverse order based on publication year and title:
public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books based on publication year and title in reverse order
Collections.sort(books, Comparator
.comparing(Book::getPublicationYear)
.thenComparing(Book::getTitle, Comparator.reverseOrder()));
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
In this code, we use Comparator.reverseOrder() within the thenComparing() method to sort the titles in reverse order while keeping the publication years in ascending order. This will reverse the order of the titles, resulting in a sorted list in reverse order based on publication year and title.
8) Sorting on multiple parameters using lambda expression
To sort Book objects by multiple parameters (year, price, and title) using a lambda expression in Java, you can use the Comparator interface along with the Comparator.thenComparing() method for each sorting criteria.
Here's how you can do it:
public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books by year, then by price,
// and finally by title using lambda expressions
Collections.sort(books, Comparator
.comparing(Book::getPublicationYear)
.thenComparing(Book::getPrice)
.thenComparing(Book::getTitle));
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
9. Sorting by chaining Comparators in Java 8 - Comparator.thenComparing
Before Java 8, if you wanted to sort a list of objects on multiple conditions e.g. a list of Book on price and then titles, you would have to write a special Comparator for that. There were no easy way to compose or chain multiple comparator to achieve sophisticated comparison.
Java 8 address this problem by providing thenComparing() method, and it's specialized version like theComparingInt(), thenComparingLong() and thenComparingDouble() to compare primitive properties.
By using these methods you can compare objects on multiple properties without creating special Comparator implementation for them.
For example, if you want to sort list of books on price and then within same price group, books should be sorted by their title.
You can achieve this in just one line in Java 8 as shown in following example:
public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, 15.99));
books.add(new Book("Book A", "Author X", 2000, 19.99));
// Sort the books by price and then by title using thenComparing
Collections.sort(books, Comparator
.comparing(Book::getPrice)
.thenComparing(Book::getTitle));
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
In this code, we use Comparator.comparing() to sort the Book objects by price and then use thenComparing() to sort them by title. This results in a sorted list of Book objects first by price and then by title.
10. Sorting with null values in Java 8
Suppose you are sorting a list of Books on authors but some books are without author i.e. their author is null, how do you sort those books? Should they come first in list or should them come last in list?
Well, it was tricky before Java 8 but now you have methods like java.util.Comparator.nullsLast() which allows you to keep books with no authors at the tail end of sorted list.
The nullsLast() is a static method from Comparator class, so you can also use static import to directly access this method in your class.
In sort, you can sort Book objects while handling null values using the nullsFirst() and nullsLast() methods of the Comparator interface in Java.
Here's how you can sort Book objects by multiple parameters (year, price, and title) while handling null values:
public class Main {
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("Book C", "Author Z", 2010, 24.99));
books.add(new Book("Book B", "Author Y", 1995, null)); // Null price
books.add(new Book("Book A", "Author X", null, 19.99)); // Null year
// Sort the books by year (nulls first),
// then by price (nulls last), and finally by title
Collections.sort(books, Comparator
.comparing(Book::getPublicationYear,
Comparator.nullsFirst(Comparator.naturalOrder()))
.thenComparing(Book::getPrice,
Comparator.nullsLast(Comparator.naturalOrder()))
.thenComparing(Book::getTitle));
// Print the sorted books
for (Book book : books) {
System.out.println(book);
}
}
}
In this code, we have handled null values for both publication year and price. We use Comparator.nullsFirst() to ensure that null values come before non-null values for the publication year and Comparator.nullsLast() to ensure that null values come after non-null values for the price.
That's all about sorting and Comparator in Java 8. In these 10 examples, you can see how much has changed on Comparator and how you can use new Java 8 features like lambda expression, method reference and new API methods from Comparator class to write short and effective Comparator in Java 8. The JDK 8 has really made sorting list, map or any collection class easier.
You now also has more control on how to handle null values in list while sorting. You can even chain multiple Comparators to perform more sophisticated and complex Comparison on the fly without writing separate Comparator implementation for them.
If you have to start using new code using Java 8, Sorting and Comparator seems to be the best place to start with.
Awesome article, probably your best in long time thank you
ReplyDelete