Sunday, July 21, 2024

10 Examples of Comparator, Comparable, and Sorting in Java 8

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 Example of Comparator and Sorting 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 + ")";

    }

}
In this example, the Book class implements the Comparable interface by providing an implementation for the compareTo method. We compare books based on their publication years. You can customize the comparison logic according to your sorting requirements.

Here's an example of how to sort a list of Book objects using the Collections.sort() method in their natural order defined by compareTo() method in Java:

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);
        }
    }
}

This will output:

Book Clean Code by Author Uncle Bob (1995)
Book Effective Java by Josh Bloch (2000)
Book Refactoring by Martin Flower (2010)

You can easily modify the compareTo method to sort the Book objects based on other criteria such as title or author but we will use custom Comparator for that purpose. You will see all those in coming examples:

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;

            }

        });
In this updated code, we use an anonymous Comparator to specify the comparison logic for sorting Book objects. The compare method within the anonymous Comparator first compares books based on publication year and then, if the publication years are the same, it compares them based on the book title. This way, the books are sorted first by publication year and then by title.


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);

        }

    }

}
In this code, we use the sort method directly on the List of Book objects, and we use the Comparator.comparing() method along with a lambda expression Book::getPublicationYear to specify the sorting criteria. 

This sorts the Book objects by publication year. You can easily modify the code to sort by other criteria like price or title by changing the lambda expression inside Comparator.comparing() method.

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. 

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 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);

        }

    }

}


In this example, we define two static methods within the Book class (compareByPrice() and compareByTitle() to compare books by price and title, respectively. Then, we use static method references Book::compareByPrice and Book::compareByTitle to create Comparator instances, which are used to sort the Book objects by price and then by title.

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);

        }

    }

}
In this code, we use the Comparator interface with the Comparator.comparing() method and Comparator.thenComparing() method to specify the multiple sorting criteria. We first sort by publication year, then by price, and finally by title. This will give you a sorted list of Book objects based on all three criteria.


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. 


This results in a sorted list of Book objects based on year (nulls first), price (nulls last), and title.

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.

1 comment:

  1. Awesome article, probably your best in long time thank you

    ReplyDelete

Feel free to comment, ask questions if you have any doubt.