Java 8 Comparator Example Using Lambda Expressions

In short, You can implement Comparator using lambda expression because it is a SAM type interface. It just got one abstract method compare() which means you can pass lambda expression where a Comparator is expected. Many Java programmer often ask me, what is the best way to learn lambda expression of Java 8? Of course by using it on your day to day programming task. Since implementing equals(), hashcode(), compareTo(), and compare() methods are some of the most common tasks of a Java developer, it makes sense to learn how to use the lambda expression to implement Comparable and custom Comparator in Java. One question comes to mind, can we use lambda expression with Comparator? because it's an old interface and may not implement functional interface annotated with @FunctionalInterface annotation?


The answer to that question is Yes, you can use a lambda expression to implement Comparator and Comparable interface in Java, and not just these two but to implement any interface, which just got one abstract method, remember from Java 8 interface can have non-abstract methods as well e.g. default and static methods. That's why lambda expression in Java 8 is known as SAM type, where SAM stands for Single Abstract Method.


This was a very important decision Java designers made, which makes it lambdas even more useful. Due to this, you can use lambda expressions with Runnable, ActionListener, and several other interfaces which got just one abstract method. You should read Mastering Lambdas: Java Programming in a Multicore World, to learn more about why lambda expression was introduced in Java and benefits of using lambdas in Java code.

How to implement Comparator in java 8 using lambda expression




By the way, you don't need to  worry in case of Comparator, because it has been made to implement the @FunctionalInterface as shown below:


@FunctionalInterfaces
public interface Comparator<T> {
 ....
}

This code snippet is from JDK 1.8, if you are using Netbeans you can open this class by typing Ctrl+O and if you are using Eclipse you open this class by using Open type shortcut Ctrl+Shift+T. See here for more useful Eclipse shortcuts for Java Programmers.

Btw, even Runnable interface is also annotated with @FunctionalInterface as seen below:

@FunctionalInterface
public interface Runnable {
   .......
}

but yes ActionListener is not annotated with @FunctionalInterface, but you can still use it in lambda expressions because it just got one abstract method called actionPerformed()

public interface ActionListener extends EventListener {

    /**
     * Invoked when an action occurs.
     */
    public void actionPerformed(ActionEvent e);

}

Earlier we have seen some hands-on examples of Java 8 Streams, here we will learn how to use lambda expression by implementing Comparator interface in Java. This will make creating custom Comparator very easy and reduce lots of boilerplate code.

By the way, Mastering Lambdas only covers lambda expression and streams, it doesn't cover all other Java 8 features e.g. new Date and Time API, new JavaScript engine and other small enhancements like Base64 encoder-decoder and performance improvements. For a complete Java 8 learning, I recommend Java SE 8 for Reall Impatient by Cay S. Horstmann, one of the best books to learn Java 8.

Java 8 Comparator Example with Lambdas




How to implement Comparator using Java 8 Lambda Expression

As I said before using lambdas to implement Comparator is a good way to learn how lambda expression work in Java. Since lambda expression in Java is SAM type (Single Abstract Method) you can use it with any interface which got just one method e.g. Comparator, Comparable, Runnable, Callable, ActionListener and so on.

Earlier we used to use Anonymous class to implement these one method interfaces, mostly when we want to pass them to a method or just want to use locally e.g. creating a thread for some temporary task, or handling the event. Now we can use a lambda expression to implement these methods, In these cases, lambdas work exactly like an anonymous class but without the heavy dose of boilerplate code required before.

How to implement Comparator in Java 8 using lambdas


In the following example, we have an object called TrainingCourse, which represent a typical training course from institutes. For simplicity, it just got two attributes title and price, where the title is String and price is BigDecimal, because float and double are not good for exact calculations.  Now we have a list of training courses and our task is to sort based on their price or based upon their title. Ideally, TrainingCourse class should implement the Comparable interface and sort training courses by their title, i.e. their natural order. Anyway, we are not doing that here to focus purely on Comparator.

To complete these task we need to create two custom Comparator implementation, one to sort TrainingCourse by title and other to sort it by price. To show the stark difference in the number of lines of code you need to do this prior to Java 8 and in JDK 1.8, I have implemented that two Comparator first using Anonymous class and later using the lambda expression. You can see that by using lambdas implementing Comparator just take one line and you can even do that on method invocation, without sacrificing readability.

This is the main reason, why you should use the lambda expression to implement Comparator, Runnable, Callable or ActionListener, they make your code more readable and terse.

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
 * How to sort Objects in Java 8, by implementing Comparator using lambda
 * expression.
 *
 * @author WINDOWS 8
 *
 */
public class ComparatorUsingLambdas{

    public static void main(String args[]) {

        // list of training courses, our target is to sort these courses
        // based upon their price or title
        List<TrainingCourses> onlineCourses = new ArrayList<>();
        onlineCourses.add(new TrainingCourses("Java", new BigDecimal("200")));
        onlineCourses.add(new TrainingCourses("Scala", new BigDecimal("300")));
        onlineCourses.add(new TrainingCourses("Spring", new BigDecimal("250")));
        onlineCourses.add(new TrainingCourses("NoSQL", new BigDecimal("310")));


        // Creating Comparator to compare Price of training courses
        final Comparator<TrainingCourses> PRICE_COMPARATOR = new Comparator<TrainingCourses>() {
            @Override
            public int compare(TrainingCourses t1, TrainingCourses t2) {
                return t1.price().compareTo(t2.price());
            }
        };


        // Comparator to compare title of courses
        final Comparator<TrainingCourses> TITLE_COMPARATOR = new Comparator<TrainingCourses>() {
            @Override
            public int compare(TrainingCourses c1, TrainingCourses c2) {
                return c1.title().compareTo(c2.title());
            }
        };


        // sorting objects using Comparator by price
        System.out.println("List of training courses, before sorting");
        System.out.println(onlineCourses);
        Collections.sort(onlineCourses, PRICE_COMPARATOR);
       
        System.out.println("After sorting by price, increasing order");
        System.out.println(onlineCourses);
        System.out.println("Sorting list by title ");      
       Collections.sort(onlineCourses, TITLE_COMPARATOR);
        System.out.println(onlineCourses);


        // Now let's see how less code you need to write if you use
        // lambda expression from Java 8, in place of anonymous class
        // we don't need an extra line to declare comparator, we can
        // provide them in place to sort() method.
       
 
        System.out.println("Sorting objects in decreasing order of price, using lambdas");
        Collections.sort(onlineCourses, (c1, c2) -> c2.price().compareTo(c1.price()));
        System.out.println(onlineCourses);
       
        System.out.println("Sorting list in decreasing order of title, using lambdas");
        Collections.sort(onlineCourses, (c1, c2) -> c2.title().compareTo(c1.title()));
        System.out.println(onlineCourses);
    }
}

class TrainingCourses {
    private final String title;
    private final BigDecimal price;

    public TrainingCourses(String title, BigDecimal price) {
        super();
        this.title = title;
        this.price = price;
    }

    public String title() {
        return title;
    }

    public BigDecimal price() {
        return price;
    }

    @Override
    public String toString() {
        return String.format("%s : %s", title, price);
    }
}

Output:
List of training courses, before sorting
[Java : 200, Scala : 300, Spring : 250, NoSQL : 310]
After sorting by price, increasing order
[Java : 200, Spring : 250, Scala : 300, NoSQL : 310]
Sorting list by title
[Java : 200, NoSQL : 310, Scala : 300, Spring : 250]
Sorting objects in decreasing order of price, using lambdas
[NoSQL : 310, Scala : 300, Spring : 250, Java : 200]
Sorting list in decreasing order of title, using lambdas
[Spring : 250, Scala : 300, NoSQL : 310, Java : 200]


By the way, you can even do better by leveraging new methods added on Comparator interface in Java 8 and by using method references as shown below:

Java 8 Comparator example


That's all on how to implement Comparator using Java 8 lambda expression. You can see it take very less code to create custom Comparator using lambdas than anonymous class. From Java 8 there is no point using anonymous class anymore, in fact, use lambdas wherever you used to use Anonymous class. Make sure you implement SAM interfaces using lambdas e.g. Runnable, Callable, ActionListener etc. If you want to learn Java 8, then you can also see this list of some of the Best Java 8 tutorials.


4 comments:

  1. You can do even better:

    Collections.sort(onlineCourses, Comparator.comparing(TrainingCourse::title).reversed())

    ReplyDelete
    Replies
    1. Wow, the great Cay Horstmann.. I am honored with your visit to my humble blog. Thanks for dropping by and indeed method reference can make the code even more succinct.

      Delete
  2. Java 8 rocks!! not just Comparator but any other class which has single abstract method can be re-written using lambda expression and method references e.g. ActionListener, Runnable and Callable interfaces.

    ReplyDelete
  3. I tried implementing Comparable by using Lambda Expression, but no luck as it is using state of the object in comparison and not just the parameter object. It would be great if you provide an example to implement Comparable interface by using Lambda Expression.

    ReplyDelete