Sunday, April 2, 2023

7 Examples of Comparator and Comparable in Java 8

Hello guys, you may know that Java 8 introduced a number of features to simplify the code and make it more readable. One such feature is enhancements of Comparator and Comparable interfaces. The Comparator interface provides a way to compare two objects and sort them based on specific criteria. The Comparable interface is used to compare the objects of the same class and sort them based on their natural order. In this article, we'll explore 7 different examples of Comparator and Comparable in Java 8. We'll see how we can use them to sort objects based on various criteria, such as alphabetical order, length, age, and salary. These examples will give you a better understanding of how Comparator and Comparable work and how you can use them to improve your code.

The JDK 8 release has added many useful changes to the classes you use on your day-to-day programming e.g. List, Map, Collection, Iterable, and Comparator. The java.util.Comparator is one of the luckiest class in JDK 8, it has got a host of new powerful methods, which has completely changed the way you compare objects. 

For example, by using comparing() method it's easier to compare objects on any particular field and by using thenComparing() method you can easily chain multiple comparator to provide a more realistic but complex ordering. 

It also provide several utility methods to reverse the order of comparator, sort objects on natural order and making a comparator null safe by ordering null either and first or last position using nullsFirst() and nullsLast() methods. Most of these methods work well with lambdas and method reference which eventually lead to a more cleaner code while sorting list or array of objects in Java 8. 

7 Ways to use Comparator and Comparable in Java 8

Here is my list of selected examples for comparing objects in Java 8. These examples not only includes the new way of writing comparators using lambdas and method reference but also how to leverage new Comparator methods e.g. comparing(), thenComapring(), reversed(), naturalOrder(), nullsFirst() etc to provide sophisticate ordering and sorting in Java 8. 

The object we'll sort
In order to demonstrate use of Comparator and Comparable in Java 8, we need a domain object. I like books, so I'll create a Book object with some fields to demonstrate how you can sort a list of books using Java 8 features. Here is how our Book object will like:

public class Book implements Comparable < Book > {
  private String title;
  private String author;
  private int price;

  public Book(String title, String author, int price) {
    this.title = title;
    this.author = author;
    this.price = price;
  }

  public String getTitle() {
    return title;
  }

  public String getAuthor() {
    return author;
  }

  public int getPrice() {
    return price;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public void setAuthor(String author) {
    this.author = author;
  }

  public void setPrice(int price) {
    this.price = price;
  }

  @Override
  public String toString() {
    return "Book [title=" + title + ", author=" + author + ", price=" +
      price + "]";
  }

  // the old way to implement CompareTo method to compare
  // object by multiple fields, you'll learn new way as well
  @Override
  public int compareTo(Book b) {
    int i = this.title.compareTo(b.title);
    if (i != 0) return i;

    i = this.author.compareTo(b.author);
    if (i != 0) return i;

    return Integer.compare(this.price, b.price);
  }

}
and here is the list of Books which we'll sort in this article:

List<Book> listOfBooks = new ArrayList<>();
listOfBooks.add(new Book("Effective Java", "Joshua Bloch", 32));
listOfBooks.add(new Book("Java Puzzlers", "Joshua Bloch", 22));
listOfBooks.add(new Book("Java Concurrency in Practice", "Brian Goetz", 42));
listOfBooks.add(new Book("Java SE 8 for Really Impatient", "Cay S. Horstmann", 34));
listOfBooks.add(new Book("Core Java", "Cay S. Horstmann",32));

Here is an example how Comparator interface work:

The Comparator interface has a single method, compare(), which returns a positive number if the first object is greater than the second, a zero if they are equal, and a negative number if the first object is less than the second.

7 Examples of Comparator and Comparable in Java 8


This diagram shows the different steps involved in using a Comparator in Java to sort an array of objects using Collections.sort(). First, the array of objects is passed to Collections.sort(), which sorts the objects based on the compare() method implemented by the Comparator.



7 Advanced Example of Comparator and Comparable in Java for Sorting Objects

Now, let's see how we can sort this list of objects using new features of Java 8 and new methods added on the Comparator class.


1. Writing Comparator using Lambda Expression

While learning Java 8, the first thing a Java developer should learn is to implement SAM interfaces using lambda expressions. Sine Comparator and Comparable is also a SAM interfaces e.g. they contain just one abstract method i.e. compare() and compareTo(), you can easily implement them using lambda expression. For example, if you want to write a Comparator to sort Books by their author, you can write lik in following example:

Comparator<Book> byAuthor = (b1, b2) -> b1.getAuthor().compareTo(b2.getAuthor());

just compare this to the old Java SE 7 way of writing comparator in Java:

Comparator<Book> byAuthorOldWay = new Comparator<Book>(){

public int compare(Book b1, Book b2){
  return b1.getAuthor().compareTo(b2.getAuthor());
}

};

You can see, in Java 8, you can write Comparator using lambda expression in Just one line.Now, you can sort the list of books by using this comparator either by using Collections.sort() method or newly added List.sort() method, which sort the list in place, as shown below:

listOfBooks.sort(byAuthor);
System.out.println("list of books after sorting: " + listOfBooks);

This will print following output:

list of books after sorting: 
[Book [title=Java Concurrency in Practice, author=Brian Goetz, price=42], 
Book [title=Java SE 8 for Really Impatient, author=Cay S. Horstmann, price=34], 
Book [title=Core Java, author=Cay S. Horstmann, price=32], 
Book [title=Effective Java, author=Joshua Bloch, price=32], 
Book [title=Java Puzzlers, author=Joshua Bloch, price=22]]

You can see that Brian Goetz book top the list because "B" comes first in lexicographic order, followed by Cay. S. Horstmann book and finally Josh bloch's books. 

So, you have now learned how to create comparator using lambda expression in Java 8 in just one line, simple and easy right? but Wait.. Things will get even more simpler when we'll start using method reference in next example. 




2. Writing Comparator using Method Reference

When you are simply accessing a property of an object in lambda expression then you can replace the reference to lambdas by method reference. This result in more cleaner and readable code than you ever seen in Java. For example, we can write the Comparator of previous example using method refrence as shown below:
Comparator<Book> byAuthor = Comparator.comparing(Book::getAuthor);

This way, it's very easy to create different Comparator for sorting into different parameters e.g. let's create Comparator which sorts byTitle and byPrice as well:

Comparator<Book> byTitle = Comparator.comparing(Book::getTitle);
Comparator<Book> byPrice = Comparator.comparing(Book::getPrice);

When it comes to sorting, you can also directly pass the Comparator to Collections.sort() or List.sort() method as shown in following example:

listOfBooks.sort(Comparator.comparing(Book::getPrice)); 
System.out.println("list of books after sorting by price: " + listOfBooks);

Output
list of books after sorting by price: 
[Book [title=Java Puzzlers, author=Joshua Bloch, price=22],
 Book [title=Effective Java, author=Joshua Bloch, price=32], 
Book [title=Core Java, author=Cay S. Horstmann, price=32], 
Book [title=Java SE 8 for Really Impatient, author=Cay S. Horstmann, price=34], 
Book [title=Java Concurrency in Practice, author=Brian Goetz, price=42]]


3. Chaining Comparators to compare multiple fields

If you are amazed to see the previous example of Comparator, you will be even more surprised to see this example, which demonstrate another magic of Java 8 which allows you to chain methods to perform sophisticated comparison logic. 

One of the common requirement in real world is to compare objects by multiple fields e.g. first compare by author and if author is same the compare by title or price. You can use thenComparing() method to chain multiple Comparators in Java 8 to compare objects by multiple fields e.g. comparing a list of person by name and by age or comparing a list of books by author and price as shown in following example:

Comparator<Book> byAuthorThenByPrice 
= Comparator.comparing(Book::getAuthor).thenComparing(Book::getPrice);

You can then use this comparator to sort a list of objects by multiple fields as shown below:

listOfBooks.sort(Comparator.comparing(Book::getAuthor)
.thenComparing(Book::getPrice));

System.out.println("sorting list of books by multiple fields, 
by author and then by price: " + listOfBooks);

Output:
sorting list of books by multiple fields, by author and then by price:
[Book [title=Java Concurrency in Practice, author=Brian Goetz, price=42], 
Book [title=Core Java, author=Cay S. Horstmann, price=32], 
Book [title=Java SE 8 for Really Impatient, author=Cay S. Horstmann, price=34], 
Book [title=Java Puzzlers, author=Joshua Bloch, price=22], 
Book [title=Effective Java, author=Joshua Bloch, price=32]]

You can see that the list of book is first sorted by author and that's why Brian Goetz's book come first and then next two books are from Cay S. Horstmann, where the book with less price comes first i.e. Core Java comes before Java SE 8 for Really Impatient.


4. Comparing in reverse order of Comparator

The combination of comparing() method and method reference offers several possibility. One of them is to create a reverse order comparator. The JDK 8 API also helps as it provides a new reversed() method in Comparator class which returns a comparator that imposes the reverse ordering of this comparator. You can use this method to compare objects in the reverse order of any Comparator as shown in following example:

listOfBooks.sort(Comparator.comparing(Book::getAuthor).reversed());

Here we are sorting list of books on reverse order of author i.e. it will print Book object in the descending order of author as seen below:

sorting list in reverse order of authors: 
[Book [title=Effective Java, author=Joshua Bloch, price=32], 
Book [title=Java Puzzlers, author=Joshua Bloch, price=22], 
Book [title=Java SE 8 for Really Impatient, author=Cay S. Horstmann, price=34], 
Book [title=Core Java, author=Cay S. Horstmann, price=32], 
Book [title=Java Concurrency in Practice, author=Brian Goetz, price=42]]

You can see that Joshua Bloch's Effective Java comes first now and Brian Goetz's Java Concurrency in Practice comes last. 


5. Comparing objects by natural order

It's easy to compare objects by their natural order by using JDK 8's Comparator.naturalOrder() method. It provides the same ordering provided by the Comparable interface. You can pass this comparator to Collections.sort() or List.sort() method to sort a list of objects by their natural order. 

If you look at the code of Book class, you will see that compareTo() method first compare books by title and if title is same then by author and if author is same then by price. Let's see an example of sorting objects on natural order using Comparator.naturalOrder()

listOfBooks.sort(Comparator.naturalOrder());
System.out.println("sorting list of books in their natural order using 
Comparator.naturalOrder(): " + listOfBooks); 

Output:
sorting list of books in their natural order using Comparator.naturalOrder(): 
[Book [title=Core Java, author=Cay S. Horstmann, price=32], 
Book [title=Effective Java, author=Joshua Bloch, price=32], 
Book [title=Java Concurrency in Practice, author=Brian Goetz, price=42], 
Book [title=Java Puzzlers, author=Joshua Bloch, price=22], 
Book [title=Java SE 8 for Really Impatient, author=Cay S. Horstmann, price=34]]

You can see That Core Java by Cay S. Horstmann comes first while Java SE 8 for Really Impatient by same author comes last. 

Btw, you can also sort a list in natural order by using Comparable and you can do so by not passing any Comparator to List.sort() method i.e. just passing null as shown below:

Sorting list of books in their natural order using Comparable: 
[Book [title=Core Java, author=Cay S. Horstmann, price=32], 
Book [title=Effective Java, author=Joshua Bloch, price=32], 
Book [title=Java Concurrency in Practice, author=Brian Goetz, price=42], 
Book [title=Java Puzzlers, author=Joshua Bloch, price=22], 
Book [title=Java SE 8 for Really Impatient, author=Cay S. Horstmann, price=34]]

You can see that both output is same. Btw, I prefer the first approach than second because passing null to sort method doesn't look clean. I am surprised why didn't Java API designer overloaded sort() method just like Collection.sort() where the sort() which doesn't accept any parameter sort in natural order. May be, they are just concern with number of methods introduced in the List class. 


7. Null safe comparing using nullsFirst() and nullsLast() Comparator

One of the interesting addition on Comparator interface in JDK 8 is the null-safe comparators. You can now convert your non-null-safe comparator to a null-safe Comparator by using either nullsFirst() or nullsLast() methods. 

The nullsFirst() method returns a null-friendly comparator that considers null to be less than non-null. When both are null, they are considered equal. If both are non-null, the specified Comparator is used to determine the order. If the specified comparator is null, then the returned comparator considers all non-null values to be equal. 

On the other handl nullsLast() method considers null to ge greater than non-null, hence they come last in the ascending order of objects. Let's see an example of nullsFirst() and nullsLast() method of Java 8 using Comparator. 

In order to demonstrate null safe sorting in Java 8, we first need to add a null element in the list of books, as soon as you do this your code will break with NullPointerexception because our comparator implementations are not handling nulls. If you run the code given in first example, you will seee following exception:

Exception in thread "main" java.lang.NullPointerException
at java.util.ComparableTimSort.binarySort(ComparableTimSort.java:262)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:189)
at java.util.Arrays.sort(Arrays.java:1312)
at java.util.Arrays.sort(Arrays.java:1506)
at java.util.ArrayList.sort(ArrayList.java:1454)
at Main.main(Main.java:25)

In order to solve this exception and sort a list which may contain null elements, we'll use the nullsFirst method as shown below:

Comparator<Book> byAuthor = (b1, b2) -> b1.getAuthor().compareTo(b2.getAuthor()); 
listOfBooks.sort(Comparator.nullsFirst(byAuthor)); 
System.out.println("sorting list of books with nulls first " + listOfBooks);

Output
sorting list of books with nulls first 
[null, 
Book [title=Java Concurrency in Practice, author=Brian Goetz, price=42], 
Book [title=Java SE 8 for Really Impatient, author=Cay S. Horstmann, price=34], 
Book [title=Core Java, author=Cay S. Horstmann, price=32], 
Book [title=Effective Java, author=Joshua Bloch, price=32], 
Book [title=Java Puzzlers, author=Joshua Bloch, price=22]]

You can see that NullPointerException has gone away and null has come first in the sorted order. If you use the nullsLast() method then null will come last in the sorting order. The big benefit of using this method is that now you can easily sort a list of objects without worrying about nuls. Your Comparator is also free from any null pointer handling logic.


That's all about some of the essential Comparator and Comparable examples in Java 8. You can see that JDK 8 has really made Comparator class more useful and with the help of lambda expression and method reference, it's become super easy to provide a Comparator implementation on the fly. 

You don't need to write boilerplate code which comes with Anonymous inner class, both lambdas and method reference allow you to write clean code for comparing and sorting objects in Java 8. 

There are several things which has become easier in Java e.g. comparing objects by multiple parameters. Earlier, you used to write several lines of code to implement ordering on multiple fields but now its become easier due to comparing() and thenComparing() methods of Java 8 Comparator class, which allows you to compose a complex ordering by chaining multiple comparator. 

Last, but not the least, you can now safely handle nulls in the list while sorting. By using nullsFirst or nullsLast comparator you can put null either at first or last position while sorting a list of objects containing null elements. 

If you are eager to learn more about new enhancement made on other key Java classes e.g. Map or List, I suggest you to read Java SE 8 for Really Impatient. It includes a nice summerly of miscellaneous changes of JDK8 which are useful in your day-to-day coding. 


Thanks for reading this article so far. If you like this tutorial then please share with your friends and colleagues. If you have any question, doubt or feedback about this tutorial and my explanation then please drop a comment.

1 comment: