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
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ui.dazzle(e.getModifiers()); } });
button.addActionListener(e -> { ui.dazzle(e.getModifiers()); });
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:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave"); List<String> filteredNames = names .filter(e -> e.length() >= 4) .into(new ArrayList<String>()); for (String name : filteredNames) { System.out.println(name); }
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>)
:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave"); names .filter(e -> e.length() >= 4) .forEach(e -> { System.out.println(e); });
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:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave"); names .mapped(e -> { return e.length(); }) .asIterable() // returns an Iterable of BiValue elements // an element's key is the person's name, its value is the string length .filter(e -> e.getValue() >= 4) .sorted((a, b) -> a.getValue() - b.getValue()) .forEach(e -> { System.out.println(e.getKey() + '\t' + e.getValue()); });
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.
executorService.submit(MethodReference::sayHello); private static void sayHello() { System.out.println("hello"); }
Arrays.asList("Alice", "Bob", "Charlie", "Dave").forEach(System.out::println);
java.util.functions.Factory
:
Factory<Biscuit> biscuitFactory = Biscuit::new; Biscuit biscuit = biscuitFactory.make();
Lastly, let's create a reference to a method of an arbitrary instance:
interface Accessor<BEAN, PROPERTY> { PROPERTY access(BEAN bean); } public static void main(String[] args) { Address address = new Address("29 Acacia Road", "Tunbridge Wells"); Accessor<Address, String> accessor = Address::getCity; System.out.println(accessor.access(address)); }
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:
interface Queue { Message read(); void delete(Message message); void deleteAll() default { Message message; while ((message = read()) != null) { delete(message); } } }
interface BatchQueue extends Queue { void setBatchSize(int batchSize); void deleteAll() default { setBatchSize(100); Queue.super.deleteAll(); } }
interface FastQueue extends Queue { void deleteAll(); }
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.
View comments