Day: May 8, 2017

Start Using Java 8 Lambda Expressions

Introduction

Java 8 comes with a bunch of new features for its developers. One such improvement is Lambda expressions. Lambda Expressions allows Java developers to use functions as values, and pass it as a value to a method. This might be familiar for people from functional programming background, but a bit difficult for people who tends to think in object-oriented mindset. In this article, I am going through series of examples to show how we can use lambda expressions in coding.

Prerequisites

Before you start, you need to install Java 8 in your machine and create a project using Java 8. Here I’m going describe things in more abstract-level. So you should possess knowledge on using your IDE for basic things such as creating classes, executing Java code etc.

Example Scenario

For this article I have selected a scenario where you need to go though set of books and select books based on different criteria. Those selection criteria are list all the books, list books which are novels, and list title of the books written in 20th century. So let’s start coding.

Creating entity class

First step of solving such problem is to create an entity class called Book which can represent a single instance of a book. So here it is.


public class Book {

private String name;
 private String author;
 private int year;
 private String language;
 private String category;

public Book(String name, String author, int year, String language, String category) {
 this.name = name;
 this.author = author;
 this.year = year;
 this.language = language;
 this.category = category;
 }

public String getName() {
 return name;
 }

public void setName(String name) {
 this.name = name;
 }

public String getAuthor() {
 return author;
 }

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

public int getYear() {
 return year;
 }

public void setYear(int year) {
 this.year = year;
 }

public String getLanguage() {
 return language;
 }

public void setLanguage(String language) {
 this.language = language;
 }

public String getCategory() {
 return category;
 }

public void setCategory(String category) {
 this.category = category;
 }

@Override
 public String toString() {
 return "Book{" +
 "name='" + name + '\'' +
 ", author='" + author + '\'' +
 ", year=" + year +
 ", language='" + language + '\'' +
 ", category='" + category + '\'' +
 '}';
 }
}

I think I no need to explain the above simple Java class as it should be familiar to you.

Conventional Method

This is what we usually do to solve this type of problems.

public class BookFinderExample1 {

    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
                new Book("Moby-Dick", "Herman Melville", 1851, "EN", "Novel"),
                new Book("War and Peace", "Leo Tolstoy", 1869, "RU", "Novel"),
                new Book("The Three Musketeers", "Alexandre Dumas", 1844, "FR", "Novel"),
                new Book("Les Miserables", "Victor Hugo", 1862, "FR", "Fiction"),
                new Book("Journey to the West", "Wu Cheng'en", 1592, "ZH", "Fiction"),
                new Book("Wild Swans", "Jung Chang", 1991, "ZH", "Biography"),
                new Book("The Reader", "Bernhard Schlink", 1995, "DE", "Novel"),
                new Book("Perfume", "Patrick Suskind", 1985, "DE", "Fiction")
        );

        // 1. print all books
        System.out.println("Print all books");
        printAllBooks(books);

        // 2. print all novels
        System.out.println("Print all novels");
        printAllNovels(books);

        // 3. print all books in 20th century
        System.out.println("Print all books in 20th century");
        printAllIn20thCentury(books);
    }

    private static void printAllBooks(List<Book> books) {
        for (Book book : books) {
            System.out.println(book.toString());
        }
    }

    private static void printAllNovels(List<Book> books) {
        for (Book book : books) {
            if (book.getCategory().equals("Novel"))
                System.out.println(book.toString());
        }
    }

    private static void printAllIn20thCentury(List&lt;Book&gt; books) {
        for (Book book : books) {
            if (book.getYear() > 1900 && book.getYear() < 2001)
                System.out.println(book.getName());
        }
    }
}

First it created a list of books (I won’t going to repeat this step in next examples). Then we create 3 methods which serves our purpose. And we call those methods one by one. Though this fulfills out requirement, seems that’s not a scalable solution. Once we have a new requirement, we need to create a new method and call it.

Using Generic Solution

If we carefully look into the methods we have implemented, they all do a common thing. They iterate though a given list of books, checks a condition and perform an action (eg: print the book). So we can use interfaces for this and stick with just one method. Let’s see how.

public class BookFinderExample2 {

    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
                .......
        );

        // 1. print all books
        System.out.println("Print all books");
        printBooks(books, new Checker() {
            public boolean check(Book book) {
                return true;
            }
        }, new Action() {
            public void perform(Book book) {
                System.out.println(book.toString());
            }
        });

        // 2. print all novels
        System.out.println("Print all novels");
        printBooks(books, new Checker() {
            public boolean check(Book book) {
                return book.getCategory().equals("Novel");
            }
        }, new Action() {
            public void perform(Book book) {
                System.out.println(book.toString());
            }
        });

        // 3. print all books in 20th century
        System.out.println("Print all books in 20th century");
        printBooks(books, new Checker() {
            public boolean check(Book book) {
                return (book.getYear() > 1900 && book.getYear() < 2001);
            }
        }, new Action() {
            public void perform(Book book) {
                System.out.println(book.getName());
            }
        });
    }

    private static void printBooks(List<Book> books, Checker checker, Action action) {
        for (Book book : books) {
            if (checker.check(book)) {
                action.perform(book);
            }
        }
    }

    interface Checker {
        boolean check(Book book);
    }

    interface Action {
        void perform(Book book);
    }
}

In the above solution I have introduced two interfaces and both of them contains a single method exposed. With the use of just one method printBooks and objects implementation of those two interfaces we have achieved the same results as before. Now we have taken out the action and inject in to the printBooks method. The instances of Checker and Action is created with anonymous inner classes.

The above seems to be fine for generalizing the solution but the syntax seems to be so tedious. So far we have not used anything new in Java 8. Let’s use Java 8 new features to ease our coding.

Using Lambda Expressions

Lets have a look at one of our anonymous inner class

new Action() {
    public void perform(Book book) {
        System.out.println(book.toString());
    }
}

The above is an anonymous inner class created for Action interface. Action interface has only a single method called perform. That method takes one argument and prints it. So such Anonymous Inner classes can be written as follows.

(Book book) -> System.out.println(book.toString())

Since the Action interface has only one method, we no need to say it’s name. Since it has one line of code, no need to use curly-brackets. Now with single argument, no need of using brackets, and specifying argument type.

book -> System.out.println(book.toString())

That’s a lambda expression. So let’s substitute lambda expressions.

public class BookFinderExample3 {

    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
                ....
        );

        // 1. print all books
        System.out.println("Print all books");
        printBooks(books, book ->  true, book -> System.out.println(book.toString()));

        // 2. print all novels
        System.out.println("Print all novels");
        printBooks(books, book -> book.getCategory().equals("Novel"), book -> System.out.println(book.toString()));

        // 3. print all books in 20th century
        System.out.println("Print all books in 20th century");
        printBooks(books, book ->  (book.getYear() > 1900 && book.getYear() < 2001), book -> System.out.println(book.getName()));
    }

    private static void printBooks(List<Book> books, Checker checker, Action action) {
        for (Book book : books) {
            if (checker.check(book)) {
                action.perform(book);
            }
        }
    }

    @FunctionalInterface
    interface Checker {
        boolean check(Book book);
    }

    @FunctionalInterface
    interface Action {
        void perform(Book book);
    }
}

We have introduced 2 interfaces for our work. But is it necessary? No. Java SDK developers have identified this and provide us a set of already defined interfaces inside java.util.function package. All we have to do it, reuse them!

public class BookFinderExample4 {

    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
                ...
        );

        // 1. print all books
        System.out.println("Print all books");
        printBooks(books, book ->  true, book -> System.out.println(book.toString()));

        // 2. print all novels
        System.out.println("Print all novels");
        printBooks(books, book -> book.getCategory().equals("Novel"), book -> System.out.println(book.toString()));

        // 3. print all books in 20th century
        System.out.println("Print all books in 20th century");
        printBooks(books, book ->  (book.getYear() > 1900 && book.getYear() < 2001), book -> System.out.println(book.getName()));
    }

    private static void printBooks(List<Book> books, Predicate<Book> checker, Consumer<Book> action) {
        for (Book book : books) {
            if (checker.test(book)) {
                action.accept(book);
            }
        }
    }
}

In the above example we have used two interfaces Predicate and Consumer. you can find more information about those in Oracle’s official website. You can find plenty of such interfaces which can help your coding.

Using streams

We are not going to stop our simplification from there. If we look in to the printBooks method, it iterates through the list of books and perform things mention in the interface implementations. This kind of iteration is called external iteration, because for-loop iterates the list. In Java 8, streams provide the functionality of internal iteration, which is implemented by converting the list in to a stream. The stream can be considered as a conveyor belt which brings list items one by one. We can use filters etc., to filter-out the required elements and so what every action you like. So when using streams our code would look like as follows.

public class BookFinderExample5 {

    public static void main(String[] args) {
        List<Book> books = Arrays.asList(
                ...
        );

        // 1. print all books
        System.out.println("Print all books");
        books.stream()
                .filter(book ->  true)
                .forEach(book -> System.out.println(book.toString()));

        // 2. print all novels
        System.out.println("Print all novels");
        books.stream()
                .filter(book -> book.getCategory().equals("Novel"))
                .forEach(book -> System.out.println(book.toString()));

        // 3. print all books in 20th century
        System.out.println("Print all books in 20th century");
        books.stream()
                .filter(book ->  (book.getYear() > 1900 && book.getYear() < 2001))
                .forEach(book -> System.out.println(book.getName()));
    }

}

Conclusion

We have started our example from conventional coding and step-by-step brings the Java 8 new features such as lambda expressions, streams and forEach. Similarly you can apply Java 8 concepts in your development as well. Resources in references section below will help you to find more information about Java 8 Lambda Expressions.

References

[1] Oracle’s Java 8 website : http://docs.oracle.com/javase/8/docs/

[2] Oracle’s website on util-functions : https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

[3] Java Brains Tutorial on Lambda Expressions : https://www.youtube.com/watch?v=gpIUfj3KaOc&list=PLqq-6Pq4lTTa9YGfyhyW2CqdtW9RtY-I3