The new Java Lambda Expressions

Introduction

I say ‘new’ but that’s a relative term. I should have said ‘new in Java 8’ which is out for some years now. 4 years to be more precise.

The biggest change in Java 8 is the addition of concepts from functional programming to the language. Specifically, the language added lambda expressions. If you haven’t used the new functional features yet, you’ll probably be surprised by how different your code will look from previous Java versions. The changes in Java 8 represent the biggest changes to the language ever. In many ways, it feels like you’re learning a completely new language. The question then becomes: Why do this? Why make such drastic changes to a language that’s already twenty years old and plans to maintain backward compatibility? Why make such dramatic revisions to a language that has been, by all accounts, extremely successful? Why switch to a functional paradigm after all these years of being one of the most successful object-oriented languages ever? The answer is that the software development world has changed, so languages that want to be successful in the future need to adapt as well.

Back in the mid-’90s, when Java was shiny and new, Moore’s law was still fully in force. All you had to do was wait a couple of years and your computer would double in speed. Today’s hardware no longer relies on increasing chip density for speed. Instead, even most phones have multiple cores, which means software needs to be written expecting to be run in a multiprocessor environment. Functional programming, with its emphasis on “pure” functions (that return the same result given the same inputs, with no side effects) and immutability simplifies programming in parallel environments. If you don’t have any shared, mutable state, and your program can be decomposed into collections of simple functions, it is easier to understand and predict its behavior. This, however, is not an article  about Haskell, or Erlang, or Frege, or any of the other functional programming languages. This site is about Java, and the changes made to the language to add functional concepts to what is still fundamentally an object- oriented language. Java now supports lambda expressions, which are essentially methods treated as though they were first-class objects. The language also has method references, which allow you to use an existing method wherever a lambda expression is expected. In order to take advantage of lambda expressions and method references, the language also added a stream model, which produces elements and passes them through a pipeline of transformations and filters without modifying the original source. The examples  in this article describe the basic syntax for lambda expressions, method references, and functional interfaces, as well the new support for static and default methods in interfaces.

So you want to use lambda expressions in your code.

Why would you want to? The answer is simple. If you followed my Java Essential Training elsewhere in this book you already have the basic knowledge. So let’s go on and start with the new and modern  Java!

So let’s get go! I’m in a hurry to learn more. Use one of the varieties of lambda expression syntax and assign the result to a reference of functional interface type.

A functional interface is an interface with a single abstract method (SAM). A class implements any interface by providing implementations for all the methods in it. This can be done with a top-level class, an inner class, or even an anonymous inner class. For example, consider the Runnable interface, which has been in Java since version 1.0. It contains a single abstract method called run, which takes no arguments and returns void. It has been one of the methods to make Java Applications multi threading (eg running with multiple threads) since the beginning of the Java programming language. Now let’s do this the lamda way:

Example 1. Anonymous inner class implementation of Runnable

public class RunnableDemo { public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() { System.out.println(
            }
        }).start();
} }
"inside runnable using an anonymous inner class");

As you can see here the Thread class constructor takes a Runnable as an argument. The anonymous inner class syntax consists of the word new followed by the Runnable interface name and parentheses, implying that you’re defining a class without an explicit name that implements that interface. The code in the braces ({}) then overrides the run method, which simply prints a string to the console.

Let’s look at another example:

Example 2. Using a lambda expression in a read constructor

new Thread(() -> System.out.println( "inside Thread constructor using lambda")).start();

The syntax uses an arrow to separate the arguments (since there are zero arguments here, only a pair of empty parentheses is used) from the body. In this case, the body consists of a single line, so no braces are required. This is known as an expression lambda. Whatever value the expression evaluates to is returned automatically. In this case, since println returns void, the return from the expression is also void, which matches the return type of the run method.

A lambda expression must match the argument types and return type in the signature of the single abstract method in the interface. This is called being compatible with the method signature. The lambda expression is thus the implementation of the interface method, and can also be assigned to a reference of that interface type.

One more for the road:The next example shows the lambda assigned to a variable.

Example 3. Assigning a lambda expression to a variable

Runnable r = () -> System.out.println( "lambda expression implementing the run method"); new Thread(r).start();

There is no class in the Java library called Lambda. Lambda expressions can only be assigned to functional interface references.

Assigning a lambda to the functional interface is the same as saying the lambda is the implementation of the single abstract method inside it. You can think of the lambda as the body of an anonymous inner class that implements the interface. That is why the lambda must be compatible with the abstract method; its argument types and return type must match the signature of that method. Notably, however, the name of the method being implemented is not important. It does not appear anywhere as part of the lambda expression syntax. This example was especially simple because the run method takes no arguments and returns void. Consider instead the functional interface java.io.FilenameFilter, which again has been part of the Java standard library since version 1.0. Instances of FilenameFilter are used as arguments to the File.list method to restrict the returned files to only those that satisfy the method. From the Javadocs, the FilenameFilter class contains the single abstract method accept, with the following:

boolean accept(File dir, String name)

The File argument is the directory in which the file is found, and the String name is the name of the file. The code in Example 4 implements FilenameFilter using an anonymous inner class to return only Java source files.

Example 1-4. An anonymous inner class implementation of FilenameFilter

File directory = new File("./src/main/java"); String[] names = directory.list(new FilenameFilter() { 
@Override 
public boolean accept(File dir, String name) { return name.endsWith(".java"); } }); System.out.println(Arrays.asList(names));

In this case, the accept method returns true if the filename ends with .java and false otherwise.

The lambda expression version is shown in Example 5.

Example 5. Lambda expression implementing FilenameFilter File

directory = new File("./src/main/java"); String[] names = directory.list((dir, name) -> name.endsWith(".java")); System.out.println(Arrays.asList(names)); }

As you can see the resulting code is much simpler. This time the arguments are contained within parentheses, but do not have types declared. At compile time, the compiler knows that the list method takes an argument of type FilenameFilter, and therefore knows the signature of its single abstract method (accept). It therefore knows that the arguments to accept are a File and a String, so that the compatible lambda expression arguments must match those types. The return type on accept is a boolean, so the expression to the right of the arrow must also return a boolean. If you wish to specify the data types in the code, you are free to do so, as in Example 6.

Example 6. Lambda expression with explicit data types:

 File directory = new File("./src/main/java"); String[] names = directory.list((File dir, String name) -> name.endsWith(".java"));

Finally, if the implementation of the lambda requires more than one line, you need to use braces and an explicit return statement, as shown in Example 7.

Example 1-7. A block lambda

File directory = new File("./src/main/java"); String[] names = directory.list((File dir, String name) -> { return name.endsWith(".java"); }); System.out.println(Arrays.asList(names));

This is known as a block lambda. In this case the body still consists of a single line, but the braces now allow for multiple statements. The return keyword is now required. Lambda expressions never exist alone. There is always a context for the expression, which indicates the functional interface to which the expression is assigned. A lambda can be an argument to a method, a return type from a method, or assigned to a reference. In each case, the type of the assignment must be a functional interface.

Conclusion

The big difference between method reference and lambda expression is method reference references to the method directly it should invoke. The lambda is responsible for invoking a method (in case there is a call to a method inside the lambda body). It means we reduced the number of method calls from two to one!

Read more about Java 8: Streams

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.