JVM Languages

Lambda Expressions in Java 8

By Cay S. Horstmann, March 25, 2014

The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. Here's how it works.

You have just seen one form of lambda expressions in Java: parameters, the -> arrow, and an expression. If the code carries out a computation that doesn't fit in a single expression, write it exactly like you would have written a method: enclosed in {} and with explicit return statements. For example,

Note that it is illegal for a lambda expression to return a value in some branches but not in others. For example, (int x) -> { if (x >= 0) return 1; } is invalid.

Functional Interfaces

As we discussed, there are many existing interfaces in Java that encapsulate blocks of code, such as Runnable or Comparator. Lambdas are backwards compatible with these interfaces.

You can supply a lambda expression whenever an object of an interface with a single abstract method is expected. Such an interface is called a functional interface.

You may wonder why a functional interface must have a single abstract method. Aren't all methods in an interface abstract? Actually, it has always been possible for an interface to redeclare methods from the Object class such as toString or clone, and these declarations do not make the methods abstract. (Some interfaces in the Java API redeclare Object methods in order to attach javadoc comments. Check out the Comparator API for an example.) More importantly, as you will see shortly, in Java 8, interfaces can declare non-abstract methods.

To demonstrate the conversion to a functional interface, consider the Arrays.sort method. Its second parameter requires an instance of Comparator, an interface with a single method. Simply supply a lambda:

Behind the scenes, the Arrays.sort method receives an object of some class that implements Comparator<String>. Invoking the compare method on that object executes the body of the lambda expression. The management of these objects and classes is completely implementation dependent, and it can be much more efficient than using traditional inner classes. It is best to think of a lambda expression as a function, not an object, and to accept that it can be passed to a functional interface.

This conversion to interfaces is what makes lambda expressions so compelling. The syntax is short and simple. Here is another example:

In fact, conversion to a functional interface is the only thing that you can do with a lambda expression in Java. In other programming languages that support function literals, you can declare function types such as (String, String) -> int, declare variables of those types, and use the variables to save function expressions. In Java, you can't even assign a lambda expression to a variable of type Object because Objectis not a functional interface. The Java designers decided to stick strictly with the familiar concept of interfaces instead of adding function types to the language.

The Java API defines several generic functional interfaces in the java.util.function package. One of the interfaces, BiFunction<T, U, R>, describes functions with parameter types T and U and return type R. You can save our string comparison lambda in a variable of that type:

However, that does not help you with sorting. There is no Arrays.sort method that wants a BiFunction. If you have used a functional programming language before, you may find this curious. But for Java programmers, it's pretty natural. An interface such as Comparator has a specific purpose, not just a method with given parameter and return types. Java 8 retains this flavor. When you want to do something with lambda expressions, you still want to keep the purpose of the expression in mind, and have a specific functional interface for it.

The interfaces in java.util.function are used in several Java 8 APIs, and you will likely see them elsewhere in the future. But keep in mind that you can equally well convert a lambda expression into a functional interface that is a part of whatever API you use today. Also, you can tag any functional interface with the @FunctionalInterface annotation. This has two advantages. The compiler checks that the annotated entity is an interface with a single abstract method. And the javadoc page includes a statement that your interface is a functional interface. You are not required to use the annotation. Any interface with a single abstract method is, by definition, a functional interface. But using the @FunctionalInterface annotation is a good idea.

Finally, note that checked exceptions matter when a lambda is converted to an instance of a functional interface. If the body of a lambda expression can throw a checked exception, that exception needs to be declared in the abstract method of the target interface. For example, the following would be an error:

Because the Runnable.run cannot throw any exception, this assignment is illegal. To fix the error, you have two choices. You can catch the exception in the body of the lambda expression. Or you can assign the lambda to an interface whose single abstract method can throw the exception. For example, the call method of the Callable interface can throw any exception. Therefore, you can assign the lambda to a Callable<Void> (if you add a statement return null).

Method References

Sometimes, there is already a method that carries out exactly the action that you'd like to pass on to some other code. For example, suppose you simply want to print the event object whenever a button is clicked. Of course, you could call

button.setOnAction(event -> System.out.println(event));

It would be nicer if you could just pass the println method to the setOnAction method. Here is how you do that:

button.setOnAction(System.out::println);

The expression System.out::println is a method reference that is equivalent to the lambda expression x -> System.out.println(x).

As another example, suppose you want to sort strings regardless of letter case. You can pass this method expression:

Arrays.sort(strings, String::compareToIgnoreCase)

As you can see from these examples, the :: operator separates the method name from the name of an object or class. There are three principal cases:

object::instanceMethod

Class::staticMethod

Class::instanceMethod

In the first two cases, the method reference is equivalent to a lambda expression that supplies the parameters of the method. As already mentioned, System.out::println is equivalent to x -> System.out.println(x). Similarly, Math::pow is equivalent to (x, y) -> Math.pow(x, y). In the third case, the first parameter becomes the target of the method. For example, String::compareToIgnoreCase is the same as (x, y) -> x.compareToIgnoreCase(y).

When there are multiple overloaded methods with the same name, the compiler will try to find from the context which one you mean. For example, there are two versions of the Math.max method, one for integers and one for double values. Which one gets picked depends on the method parameters of the functional interface to which Math::max is converted. Just like lambda expressions, method references don't live in isolation. They are always turned into instances of functional interfaces.

You can capture the this parameter in a method reference. For example, this::equals is the same as x -> this.equals(x). It is also valid to use super. The method expression super::instanceMethod uses this as the target and invokes the superclass version of the given method. Here is an artificial example that shows the mechanics:

When the thread starts, its Runnable is invoked, and super::greet is executed, calling the greet method of the superclass. (Note that in an inner class, you can capture the this reference of an enclosing class as EnclosingClass.this::method or EnclosingClass.super::method.)

Constructor References

Constructor references are just like method references, except that the name of the method is new. For example, Button::new is a reference to a Button constructor. Which constructor? It depends on the context. Suppose you have a list of strings. Then, you can turn it into an array of buttons, by calling the constructor on each of the strings, with the following invocation:

Details of the stream, map, and collect methods are beyond the scope of this article. For now, what's important is that the map method calls the Button(String) constructor for each list element. There are multiple Buttonconstructors, but the compiler picks the one with a String parameter because it infers from the context that the constructor is called with a string.

You can form constructor references with array types. For example, int[]::new is a constructor reference with one parameter: the length of the array. It is equivalent to the lambda expression x -> new int[x].

Array constructor references are useful to overcome a limitation of Java. It is not possible to construct an array of a generic type T. The expression new T[n] is an error since it would be erased to new Object[n]. That is a problem for library authors. For example, suppose we want to have an array of buttons. The Stream interface has a toArray method that returns an Object array:

Object[] buttons = stream.toArray();

But that is unsatisfactory. The user wants an array of buttons, not objects. The stream library solves that problem with constructor references. Pass Button[]::new to the toArray method:

Button[] buttons = stream.toArray(Button[]::new);

The toArray method invokes this constructor to obtain an array of the correct type. Then it fills and returns the array.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!