Top 5 Sorting Examples of Comparator and Comparable in Java 8

The JDK 8 release has completely changed the way you compare objects and sort them in Java. The new features of Java 8 language e.g. lambda expression and method reference has made it easier to implement both Comparator and Comparable interface, as you don't need Anonymous class for inline implementation. Now, you can create Comparators in just one line by using lambdas and method reference as we'll see in this article. Other features like providing default and static methods on interfaces have also made a huge difference when it comes to Comparator. They helped Java API designer to redesign and evolve existing interfaces, which wasn't possible earlier without breaking existing clients of those interfaces.

The JDK 8 release has added many useful changes to the classes you use on your day-to-day programming like 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 comparators to provide a more realistic but complex ordering.

It also provides 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 the 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.  You can further join The Complete Java MasterClass to learn more about the modern style of coding in Java.




6 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 sophisticated ordering and sorting in Java 8.

The object we'll sort

In order to demonstrate the 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:

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));
 
Now, let's see how we can sort this list of objects using new features of Java 8 and new methods added on 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. Since Comparator and Comparable are also SAM interfaces e.g. they contain just one abstract method like compare() and compareTo(), you can easily implement them using a lambda expression.

For example, if you want to write a Comparator to sort Books by their author, you can write like in the 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 sorts the list in place, as shown below:

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

This will print the 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 simpler when we'll start using method reference in the next example. Btw, if you want to learn more about lambda expression of Java 8 then you should also see What's New in Java 8 course on Pluralsight.

Top 5 Examples of Comparator and Comparable in Java 8



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 have seen in Java. For example, we can write the Comparator of the previous example using method reference 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 the 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 demonstrates another magic of Java 8 which allows you to chain methods to perform sophisticated comparison logic.

One of the common requirement in the real world is to compare objects by multiple fields e.g. first compare by the author and if the 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 like comparing a list of a person by name and by age or comparing a list of books by author and price as shown in the 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 the book is first sorted by author and that's why Brian Goetz's book comes first and then next two books are from Cay S. Horstmann, where the book with less price comes first like  Core Java comes before Java SE 8 for Really Impatient, one of my favorite books to learn Java 8.

Sorting Objects using Comparator and Comparable in Java 8


4. Comparing in reverse order of Comparator

The combination of comparing() method and method reference offers several possibilities. 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 the following example:

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

Here we are sorting a 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. If you haven't read them yet then they should be in your reading list for the coming weekend.


5. Comparing objects by the 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 the title is same then by author and if the 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 the 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 outputs are the 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() were the sort() which doesn't accept any parameter sort in the natural order.

Maybe, they are just concern with the number of methods introduced in the List class.
Btw, if you want to deep dive into new methods added on the existing interface like List and Map in Java 8 then The Complete Java MasterClass is a good resource.

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



6. Null-safe comparing using nullsFirst() and nullsLast() Comparator

One of the interesting addition on the 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 hand, nullsLast() method considers null to be 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 the first example, you will see the 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 nulls. 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, its 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 have 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 it become easier due to comparing() and thenComparing() methods of Java 8 Comparator class, which allows you to compose a complex ordering by chaining multiple comparators.

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 like Map or List, I suggest you read Java SE 8 for Really Impatient. It includes a nice summary of miscellaneous changes of JDK8 which are useful in your day-to-day coding.


Further Reading
What's New in Java 8
Java SE 8 for the Impatient
The Complete Java MasterClass


Related Java 8 Tutorials
If you are interested in learning more about new features of Java 8, here are my earlier articles covering some of the important concepts of Java 8
  • Java 8 Interview Questions Preparation Course (free)
  • How to use Stream class in Java 8 (tutorial)
  • Difference between abstract class and interface in Java 8? (answer)
  • 5 Books to Learn Java 8 from Scratch (books)
  • 20 Examples of Date and Time in Java 8 (tutorial)
  • How to sort the map by keys in Java 8? (example)
  • How to convert List to Map in Java 8 (solution)
  • What is the default method in Java 8? (example)
  • How to format/parse the date with LocalDateTime in Java 8? (tutorial)
  • How to use peek() method in Java 8 (example)
  • How to use filter() method in Java 8 (tutorial)
  • How to sort the may by values in Java 8? (example)
  • How to join String in Java 8 (example)
  • 5 Free Courses to learn Java 8 and 9 (courses)

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.

P. S. - If you are looking for some free courses to learn recent changes on Java 8 and Java 9 then you can also see this list of Free Java 8 and 9 Courses for Programmers.

No comments:

Post a Comment