Java 8 lambdas
Scheduled for release in 2013, Java 8 will include language support for lambda functions. Although the specification is still in flux, lambdas are already implemented in JDK 8 binaries.
This article takes a tour of the new lambda syntax, the use of lambdas in the Collections API, and related language enhancements. All code snippets were compiled with JDK 8 lambda build b39.
Functional interfaces
Interfaces that have just one method are called functional interfaces. Lambda expressions can be used anywhere we have a functional interface.
java.awt.event.ActionListener
is a functional interface since it has one method, void actionPerformed(ActionEvent)
. In Java 7 we can write
In Java 8 this becomes
Here, the compiler knows that the lambda expression must conform to the signature void actionPerformed(ActionEvent)
. It sees that the lambda body returns void, and it can infer that type of parameter e
is java.awt.event.ActionEvent
.
Functional collections
The Java 8 class library has a new package, java.util.functions
, which contains several new functional interfaces. Many of these can be used with the Collections API.
java.util.functions.Predicate
Use a predicate to filter a collection: Here we have two new methods:
Iterable<T> filter(Predicate<? super T>)
which retains only those elements for which the predicate holds true<A extends Fillable<? super T>> A into(A)
which fills theArrayList
with all the elements retained after filtering
java.util.functions.Block
We can also replace the for
loop with another new Iterable
method, void forEach(Block<? super T>)
:
The forEach()
method is an example of internal iteration: iteration happens inside the Iterable
and our Block
can see only one element at a time.
Finally, a less trivial example of functional programming with the Collections API:
Programming collections in this style has some advantages:
- Elements may be computed lazily
- If we apply a Mapper to a collection of a thousand elements but only iterate over the first three, the remaining elements will never be mapped.
- Method chaining is encouraged
- Hence there's no need to store intermediate results in their own collections.
- Internal iteration hides implementation decisions
- For example, we could parallelize a
map()
operation just by writingmyCollection.parallel().map(e ‑> e.length())
.
Method references
We can reference a method using the ::
syntax. Method references are treated the same way as lambda expressions and can be used wherever a functional interface is accepted.
java.util.functions.Factory
:
Lastly, let's create a reference to a method of an arbitrary instance:
Here we do not need to bind the method reference to an instance. Instead, we pass the instance (in this case, address
) as the first argument to the functional interface.
Default methods
With Java today, it is not possible to add methods to a published interface without breaking existing implementations. Java 8 gives us a way to specify a default implementation in the interface itself:
Subinterfaces can override a default method:
Or a subinterface can remove the default by redeclaring the method without a body:
Doing this forces an implementation of FastQueue
to implement deleteAll()
.
HotSpot implementation
Not only do lambda expressions give rise to more compact source code, but their bytecode and runtime implementation can be more efficient than the anonymous classes we would see in Java 7. For each lambda expression it finds, the compiler creates a method such as lambda$1()
. This process is called lambda body desugaring. When the lambda expression is captured, the compiler emits an invokedynamic
call site. This call site is responsible for capturing values from the lexical scope and binding the desugared method to the target functional interface.
Further reading
Much of this post is based on Brian Goetz's articles State of the Lambda, State of the Lambda: Libraries Edition and Translation of Lambda Expressions. Between them, these articles address the details of lambda syntax, variable capture, type inference and compilation.