Lambda Expressions
Goals
- Be able to recognize functional interfaces.
- Know what lambda expressions are and how to create them.
- Understand the uses of higher-order functions.
- Distinguish internal and external iteration.
Concepts
- boilerplate
- closure
- effectively final
- Faux Pas
- function
- function composition
- functional interface
- functional programming
- higher-order function
- lambda expression
- referential integrity
Language
->
Library
com.google.common.base.Predicate<T>
java.lang.Comparable<T>
java.lang.Iterable<T>
java.util.Iterable.forEach(Consumer<? super T> action)
java.lang.String.isEmpty()
java.lang.FunctionalInterface
java.lang.Collection.removeIf(Predicate<? super E> filter)
java.util.Comparator<T>
java.util.Comparator.compare(T object1, T object2)
java.util.Comparator.comparing(Function<? super T,? extends U> keyExtractor)
java.util.Comparator.comparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator)
java.util.Comparator.comparingInt(ToIntFunction<? super T> keyExtractor)
java.util.Comparator.reverseOrder()
java.util.Iterator<T>
java.util.List.sort(Comparator<? super E> comparator)
java.util.function
java.util.function.BiFunction<T, U, R>
java.util.function.DoubleConsumer
java.util.function.Consumer<T>
java.util.function.Consumer.accept(T)
java.util.function.Consumer.andThen(Consumer<? super T> after)
java.util.function.Function<T, R>
java.util.function.Function.andThen(Function<? super R, ? extends V> after)
java.util.function.Function.apply(T)
java.util.function.Function.identity()
java.util.function.IntConsumer
java.util.function.LongConsumer
java.util.function.Predicate<T>
java.util.function.Predicate.and(Predicate<? super T> other)
java.util.function.Predicate.negate()
java.util.function.Predicate.or(Predicate<? super T> other)
java.util.function.Predicate.test(T)
java.util.function.Supplier<T>
java.util.function.Supplier.get()
Preview
final List<Point> points = getPoints();
points.sort(comparing(point -> point.getX()));
Lesson
You've already learned about the strategy pattern, in which astrategyclass encapsulates the behavior required for performing some action. One of the archetypical strategy interfaces is the
Comparator<T>
, which can be passed to the List.sort(Comparator<? super E> comparator)
method to specify how sorting should be performed. The Comparator<T>
interface looks like this:
You'll notice that the Comparator<T>
interface has only a single method: Comparator.compare(T object1, T object2)
. As Java does not allow references to individual methods, it is not unreasonable to imagine that the entire Comparator<T>
interface was created just as a way to encapsulate this single compare(…)
functionality. As such the the Comparator<T>
interface, consisting of only one method, itself represents the function of comparing.
Functional Interfaces
We consequently refer to interfaces that contain a single unimplemented method as functional interfaces. As with other interfaces, functional interfaces require some implementation to perform a task. The purpose of a functional interface, however, is primarily to serve as a wrapper around some individual method—to large extent to work around Java's lack of the ability to reference some method implementation directly.
Recalling the Point
class from previous lessons, you might make a PointPlotter
strategy for placing the points on some two-dimensional plane. In fact you might even generalize the interface to simply Plottor<T>
so that someone could specify a plotter for any type (including points, rectangles, and circles):
To take advantage of the Plotter<T>
strategy, we could create a Scene
class that holds points and plots them. In this simplified example we ignore rectangles and other shapes, concentrating solely on points.
You could then create an implementation of Plotter<Point>
named SystemOutPointPlotter
, or you might decide just to use an inner class, as you only need one reusable plotter for printing to System.out
.
Lambda Expressions
You probably noticed in the implementation of SYSTEM_OUT_POINT_PLOTTER
above that most of the code is boilerplate—sections of code that appear virtually unchanged in all Plotter<T>
implementations and do not contribute to the actual logic of the implementation. In fact the actual unique code is highlighted below The other parts of the implementation the compiler could have figured out on its own, because it was obvious we were assigning the inner class to a variable of type Plotter<Point>
.
If we extract that logic out and separate the two parts with the arrow ->
operator, we produce a lambda expression which is simply a compact representation of functionality that can be executed at a later time.
(final Point point) -> System.out.println(point)
You can use this lambda
in any context that requires a Plotter<Point>
, and the Java compiler will automatically infer the appropriate boilerplate code to wrap around the code in the lambda! A lambda can be used anywhere a functional interface is called for.
A lambda is so compact that you might even want to define a throwaway
lambda inline at the point of use:
A lambda can reference variables outside the lambda expression proper. The caveat is that the variable must be effectively final, which means that, even if it isn't declared final
, there must be no way for it to change. Anonymous classes have the same restriction, except that they require an referenced variables to actually be declared final
. It's still a good idea to mark all effectively final variables as final
, to make it clear to other developers.
Here is an example of a lambda accessing a label string declared before the lambda expression:
Return Values
You can return a value from a lambda expression just as you would from any method, except that you dispense with the return
statement. Assume we have a list of Point
instances and we would like sort them based upon their X coordinates. From the lesson over the strategy pattern you already learned how to provide sorting using an anonymous class instance:
A lambda expression can perform identical functionality using a much more compact syntax.
Functions
The term functional interface
was chosen to denote those interfaces that effectively wrap around a single method and as such are similar to mathematical functions. Many appearances of the strategy pattern in fact look as if they are invoking some mathematical function:
Give me an instance of some type.
Take this value and do something with it.
Test some value.
Produce an output value based upon some input type.
Produce some output based upon two inputs.
If your use case is as general as some of these listed examples, Java has provided in the java.util.function
package a long list of functional interfaces for you to use. Here are some of the most useful ones.
Consumer<T>
accept(T)
takes a value and does something with it.Function<T, R>
apply(T)
takes an input value and returns an output value of typeR
. This is the quintessential functional interface.Predicate<T>
test(T)
takes some value, tests it, and returns theboolean
result of the test.Supplier<T>
get()
returns an instance of typeT
.
Use these and other functional interfaces instead of making your own! For example if you wanted to remove only certain elements from a collection, you might be tempted to create a special functional interface for this:
Upon closer examination, you might realize that your RemoveFilter<E>
is no more than a general functional interface for testing some value—equivalent to a Java Predicate<T>
. It would therefore be better to use the existing Predicate<T>
functional interface instead of creating your own. Your lambda expression would not need to change.
In you don't even need to write the removeFromCollection(…)
method, because Java already added a default method to the Collection<E>
interface that uses a Predicate<T>
to do the same thing: Collection.removeIf(Predicate<? super E> filter)
.
Higher-Order Functions
You no doubt noticed that functions make good use of indirection; they delegate the task of performing some logic and/or producing a value. As you learned early on, it's always possible (and sometimes useful) to add another layer of indirection. A prime example of this technique is a higher-order function, which is a function that either takes another function as a parameter or produces a function as a result.
You already know from earlier in this lesson that you can use a lambda expression to produce an instance of the Comparator<T>.compare(T, T)
functional interface:
The Comparator<T>
interface has a static method Comparator.comparing(Function<? super T,? extends U> keyExtractor)
. Its complicated-looking signature indicates that it takes a Function<T, R>
as a parameter and produces a Comparator<T>
. In this case both the parameter and the return type are functional interfaces; either of these would have made Comparator.comparing(…)
a higher order function.
But what does the Comparator.comparing(…)
method do? The return type is simple enough: Comparator.comparing(…)
must be something that creates a comparator for us. The parameter is a Function<T, R>
for extracting a key
(that is, some value to directly compare) from some other object. In terms of our Point
class, the key
would be the X coordinate—that is the value we're comparing the points on.
Thus if we pass a Function<Point, Integer>
to Comparator.comparing(…)
that takes a Point
and returns Point.getX()
, then Comparator.comparing(…)
will return a Comparator<Point>
that compares two points by their X coordinates. We can implement a Function<Point, Integer>
using the lambda expression point -> point.getX()
:
Internal Iteration
You've had quite a journey discovering various ways to iterate over a series of items. Your first stop was learning to increment an index until you reached the length of an array:
Manually manipulating an index is error-prone, not to mention tedious with all the accompanying boilerplate. You have already learned about the iterator pattern, which encapsulated the iteration functionality into a separate iterator instance that could be manipulated:
Any instance of java.lang.Iterable<T>
is able to provide a java.util.Iterator<T>
. Still you must manually step through the sequence using Iterator.hasNext()
and Iterator.next()
. The use of the enhanced for loop helps this somewhat, by letting Java work with the Iterator<T>
behind the scenes, but in the compiled code iteration nonetheless occurs by calling the iterator methods.
All of these approaches still require some logic, external to the collection being examined, that will manually step through the sequence; such approaches are referred to as external iteration.
Java also provides a way to iterate across the items in an Iterator<T>
that completely turns things around: rather than your code calling the iterator to step through the sequence, the iterator will call your code! The method Iterable.forEach(Consumer<? super T> action)
will call your implementation of the Consumer<T>
strategy again and again—passing each item in the iterator, one at a time, to the Consumer.accept(T)
method. This approach is called internal iteration because the Iterable<T>
perform the iteration internally and calls back to your action strategy.
Without lambda expressions, it would be unwieldy to provide an implementation (especially an anonymous class implementation) of Consumer<T>
to the Iterable.forEach(…)
method. But the syntax becomes simple using a lambda:
What benefits does internal iteration bring, other than allowing the code to be even more compact that before? Importantly internal iteration completely removes any remaining chance that you might make a programming error when manually stepping through a sequence using external iteration. In future lessons you will also discover how internal iteration can bring about efficiencies by reordering the items; combining or discarding unnecessary operations; or even dividing up tasks among different processors as appropriate. In short, delegating the iteration to the collection itself relieves this responsibility from the developer, allowing the developer to concentrate on the actual functionality to perform.
Drawbacks
Lambda expressions are excellent tools for making your code less verbose and more readable—and the more your code can be understood by humans, the less humans are likely to make errors in modifying the code. Nevertheless this terseness brings some drawbacks as well.
Reusability
Creating bits of one-off logic on the fly can result in a lot of repeated code, because lambdas that aren't saved cannot be reused. One way to mitigate this would be to assign a lambda to some functional interface constant.
Exceptions
Many of Java's provided functional interfaces do not declare any exceptions, making it difficult for a lambda expression to report errors. This becomes even more tedious when lambdas are used with internal iteration, as the exceptions will be thrown by the internal iteration method (e.g. Iterable.forEach(…)
) as it steps through the items internally. If you have a checked exception you need to throw, you might choose a function interface that declares checked exceptions, or write your own. Another approach would have your lambda catch any checked exceptions and rethrow them as unchecked exceptions.
Review
Summary
Common variations of lambda expressions:
() -> doSomething()
() -> {doSomething(); doSomethingElse()}
fooBar -> fooBar.doSomething()
(final FooBar fooBar) -> doSomething(fooBar)
(foo, bar) -> foo.doSomething(bar)
(foo, bar) -> foo.getResult(bar)
(foo, bar) -> {return foo.getResult(bar);}
In the Real World
- Use the ready-made functional interfaces in the
java.util.function
package as often as possible.
Think About It
- Before creating your own functional interface, see if an existing one in the
java.util.function
package meets your needs. - If your lambda gets too big, consider creating a separate class definition to contain that functionality, making it reusable.
- If you get stuck and simply cannot figure out the correct syntax for a lambda expression, try creating the equivalent anonymous class implementation of the functional interface and then extracting the relevant lambda components.
Self Evaluation
- How might overuse of lambdas risk diminishing reusability?
- How does internal iteration make it harder to work with exceptions?
Task
Implement a presentation layer method in the Booker
class specifically for presenting a list of books to the user.
- Name this method
listPublications(…)
. Note how the name of the method reflects thelist
command. - The
listPublications(…)
method and will take aCollection<>
of publications to list. - Use internal iteration to print out all the publications.
- Print the publications sorted in the following order of priority:
- By books, magazines, and journals; in that order.
- By name.
- By publication date. You will want to make your year range class implement
Comparable<T>
to make this task easier.
- Use lambda expressions for your internal iteration consumer strategy and for your comparator strategies.
See Also
- Lambda Expressions (Oracle - The Java™ Tutorials)
- Java SE 8: Lambda Quick Start (Oracle)
- Java 8 functional interfaces (Madhusudhan Konda, O'Reilly)
References
- State of the Lambda (Brian Goetz)
Resources
Acknowledgments
- Some symbols are from Font Awesome by Dave Gandy.