Lambda Expressions in Java: Angelika Langer & Klaus Kreft
Lambda Expressions in Java: Angelika Langer & Klaus Kreft
Tutorial
ISBN
All rights reserved. No part of this publication my be reproduced, stored in a retrieval system, or
transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or
otherwise, without the prior permission of the authors.
While every precaution has bee taken in the preparation of this book, the authors assume no
responsibility for errors or omissions, or for damages resulting from the use of the information
contained herein.
2
Table of Contents
TABLE OF CONTENTS ............................................................................... 3
QUESTIONS & ANSWERS .......................................................................... 4
LAMBDA EXPRESSIONS ............................................................................ 8
BACKGROUND AND A BIT OF TRIVIA ............................................................. 8
WHAT ARE LAMBDA EXPRESSIONS? ............................................................. 9
Lambda Expressions vs. Anonymous Inner Classes................................. 9
Methods vs. Functions............................................................................ 12
Representation of Lambda Expressions ................................................. 15
Functional Interfaces ............................................................................. 17
COMPARING LAMBDAS TO ANONYMOUS INNER CLASSES ........................... 20
Syntax..................................................................................................... 21
Runtime Overhead.................................................................................. 21
Variable Binding .................................................................................... 22
Scoping................................................................................................... 23
WHY DO WE NEED LAMBDAS? ..................................................................... 24
Internal vs. External Iteration................................................................ 25
Streams and Bulk Operations................................................................. 27
PROGRAMMING WITH LAMBDAS ................................................................. 29
Fluent Programming.............................................................................. 29
Imperative Approach...................................................................................... 30
Declarative Approach .................................................................................... 30
Fluent Programming ...................................................................................... 32
Execute-Around-Method Pattern ........................................................... 34
Wrap-Up ................................................................................................ 37
DEFAULT METHODS ................................................................................ 38
INTERFACE EVOLUTION .............................................................................. 38
MULTIPLE INHERITANCE AND AMBIGUITIES ............................................... 40
REFERENCE TO RELATED READING ................................................. 42
DOCUMENTATION & SPECIFICATION ........................................................... 42
CONFERENCE PRESENTATIONS .................................................................... 42
TOOL SUPPORT ............................................................................................ 43
MISCELLANEOUS ......................................................................................... 44
APPENDIX.................................................................................................... 45
SOURCE CODE OF FLUENT PROGRAMMING CASE STUDY ............................ 45
SOURCE CODE OF EXECUTE-AROUND-METHOD PATTERN CASE STUDY .... 48
INDEX ........................................................................................................... 50
3
Questions & Answers
What is the closure debate? closure debate 8
A discussion in 2006-2009 of three proposals for a
lambda-like language feature; all three proposals
were discarded.
Why does the Java programming language need multicore 8
hardware
lambda expressions?
Because many modern programming languages have
a similar language features., as a preparation for fine-
grained automated parallelization on multi-core
hardware, in particular for bulk operations on
collections.
What is a lambda expression in Java? lambda 9
expression
A concise notation for a method without a name.
What do lambda expression and anonymous inner ad-hoc 9
functionality
classes have in common?
Both are used to implement ad-hoc functionality aka
anonymous methods.
What is a method? What is a function? How do method & 11
function
they differ? difference
Functions are executed, but also passed around like
data. They do not mutate data; they just produce
new results. The order of their invocation does not
matter.
Methods are executed and may mutate data and
produce side effects. The invocation order matters.
What is a pure function as opposed to an impure pure function 13
function?
A pure function never modifies any data, whereas an
impure function may produce side effects.
How is a lambda expression represented at runtime 15
representation
runtime?
By a lambda object; both the lambda object and its
type are dynamically created by the virtual machine
at runtime.
What is the type of a lambda expression? type of 17
lambda
In isolation a lambda expression has no definite type; expression
its type depends on the context in which it appears
and is inferred by the compiler.
4
What is the target type of a lambda expression? target type 17
A type to which the lambda expression can be
converted in a given context; the target type must be
a functional interface type.
What is a functional interface? functional 17
interface
An interface with a single abstract method.
How does the syntax of lambda expressions differ syntax 20
from the syntax of anonymous inner class?
The syntax of lambda expressions is less verbose and
more readable.
For an anonymous inner class type definition and runtime 21
representation
instance creation are tied together. How are
lambda expression translated?
Creation of the lambda object and its type is
implicitly taken care of by the virtual machine; it is
done at runtime.
Anonymous inner class can have bindings to variable 22
binding
variables of the enclosing scope. Is the same true
for lambdas?
Yes, lambda expressions can capture effectively final
variables from their enclosing scope and can bind to
the enclosing instance via this and super.
An anonymous inner class is a name scope of its lexical 23
scoping
own. How about lambda expressions?
Lambda expressions are part of the scope in which
they appear; they are not scopes of their own.
What does this refer to in a lambda expression? meaning of 23
this/super
It refers to the enclosing instance (different from an
anonymous class where this refers to the inner
class's instance).
What do we need lambda expressions in Java for? collection 24
framework
To enable convenient use of the overhauled extension
collection framework in general and it parallel bulk
operations in particular.
What is a bulk operation? bulk 24
operations
An operation that concern many or all elements in a
sequence.
What is internal and external iteration? internal 25
iteration
External iteration uses an iterator for access to all
5
sequence elements. Internal iteration is performed by
the sequence itself; the user just supplies an operation
to be applied to all sequence elements.
What is a stream? streams 27
An abstraction from the JDK collection framework
that supports bulk operations with internal iteration.
There are sequential and parallel streams.
What is filter-map-reduce? filter-map- 27
reduce
Typical bulk operation on sequences with internal
iteration.
What is fluent programming? fluent 29
programming
A programming technique that chains operations, i.e.,
a style where operations return a value that allows the
invocation of another operation.
What is declarative programming? declarative 30
programming
A programming style that describe what to rather
than how to do it.
What is pipelining? pipelining 32
An optimization for chained operations. Rather than
looping over all elements in the sequence repeatedly
(per operation in the chain) the entire chain of
operations is applied to each element in just one pass
over the sequence.
What is the execute-around-method patter? execute- 33
around-
A programming technique for eliminating code method
duplication.
What is a default method? default 38
method
An interface method with a default implementation.
What are default methods intended for? interface 38
evolution
The are used for interface evolution, i.e., extending
existing interfaces with additional methods without
breaking any implementing classes.
ambiguous
Does multiple inheritance lead to problems with inheritance 40
ambiguities?
Yes, there arise ambiguities with inheritance of
default methods, but they are easily resolved using
the right syntax.
multiple
Does Java with default methods have multiple inheritance 40
inheritance (of implementation)?
6
Yes, a class can inherit non-abstract methods from
one superclass and multiple interfaces.
7
Lambda Expressions
closure debate multicore hardware
1 See Wikipedia for examples of the syntax in which these languages express lambdas:
http://en.wikipedia.org/wiki/Lambda_calculus#Lambda_calculus_and_programming_lan
guages.
2 See the "Closure Debate" at http://www.javaworld.com/javaworld/jw-06-2008/jw-06-
8
Implementation of parallel bulk operations for collections requires a
better separation of concerns, namely separating "what" is applied to all
sequence elements from "how" it is applied to the sequence elements.
And this is what lambdas are for: they provide a convenient and concise
notation for functionality, which can be passed as an argument to a bulk
operation of a collection, which in turn applies this functionality in parallel
to all its elements. lambda expression
Lambdas are implemented "ad hoc". That is, where and when they are
needed. To this regard they are similar to a Java language feature that we
have been using all along, namely anonymous inner classes. Anonymous
inner classes, too, are "ad hoc" implementations - not just of a single
method, but of an entire class with one or several methods. Both lambdas
and anonymous inner classes are typically defined for the purpose of
passing them as an argument to a method, which takes the anonymous
functionality and applies it to something.
Here is a method from the JDK that takes a piece of functionality and
applies it; it is the listFiles(FileFilter) method from class
java.io.File.
public File[] listFiles(FileFilter filter) {
String ss[] = list();
if (ss == null) return null;
9
ArrayList<File> files = new ArrayList<>();
for (String s : ss) {
File f = new File(s, this);
if ((filter == null) || filter.accept(f))
files.add(f);
}
return files.toArray(new File[files.size()]);
}
A file filter is a piece of functionality that takes a File object and returns a
boolean value. The listFiles method applies the filter that it receives as
an argument to all File objects in the directory and returns an array
containing all those File objects for which the filter returned true.
For invocation of the li stFiles method we must supply the file filter
functionality, i.e. an implementation of the FileFilter interface.
Traditionally, anonymous inner classes have been used to provide ad hoc
implementations of interfaces such as FileFilter.
Using an anonymous inner class the invocation of the listFiles method
looks like this:
File myDir = new File("\\user\\admin\\deploy");
if (myDir.isDirectory()) {
File[] files = myDir.listFiles(
new FileFilter() {
public boolean accept(File f) { return f.isFile(); }
}
);
}
10
the method. Since Java 8, we can use lambda expressions instead of
anonymous inner classes. In both cases we pass functionality to a method
like we usually pass objects to methods: This concept is known as "code-
as-data", i.e. we use code like we usually use data. Using anonymous inner
classes, we do in fact pass an object to the method. Using lambda
expressions object creation is no longer required 4 ; we just pass the lambda
to the method. That is, with lambda expression we really use "code-as-
data".
In addition to getting rid of instance creation, lambda expressions are
more convenient. Compare the alternatives:
Using an anonymous inner class it looks like this:
File[] files = myDir.listFiles(
new FileFilter() {
public boolean accept(File f) { return f.isFile(); }
}
);
4 More precisely, object creation is no longer explicit. Under the hood, a lambda
expression is eventually translated into a synthetic class type and an instance thereof. The
key difference is that the class definition and instance creation is explicitly done by the
programmer, when anonymous inner classes are used, whereas the class definition and
instance creation is implicitly done by the runtime system. Details regarding the
translation process can be found in the section on "Lambda Translation" in the Lambda
Reference document.
5 See the section on "Method References" in the Lambda Reference document.
11
Methods vs. Functions
12
modifications happen and whether data is read before or after a
modification.
Object-oriented languages such as C++, Smalltalk, Eiffel, and Java extend
the procedural approach. They bundle data and procedures into objects
with state (the fields) and behaviour (the methods). The principle of using
methods is still the same as in procedural languages: methods are invoked
and operate on data (the fields of the class to which the method belongs),
which they read and may alter. For this reason, object-oriented languages
are imperative languages, too. pure function
The idea of functions stems from functional languages such as Lisp,
Haskell, Closure, and Scala. These languages have pure functions that
behave as described above: they do not mutate data, instead they produce
results from the arguments they receive. In addition, functions are used
differently. A function, too, is invoked, but the key idea is that functions
are passed around as arguments to an operation or as the result of an
operation. This is similar to passing around data or objects in procedural
and object-oriented languages - hence the previously mentioned notion of
"code-as-data".
Functional languages are declarative as opposed to imperative. They describe
what a program should accomplish, rather than describing how to go about
accomplishing it. In a declarative language the programmer does not
dictate which steps must be performed in which order. Instead, there is a
clear separation between what is done (this is what the programmer
declares) and how it is done (this is determined elsewhere, by the language
or the implementation). In pure functional languages such as Haskell the
functions do not have any side effects. In particular, they do not modify
data or objects. If no side effects occur, order does not matter.
Consequently it is easy to achieve a clear separation between "how" and
"what" is done.
Hence there are the following differences between the concept of
methods and pure functions :
13
Methods are used Pure functions are used
Invocation
imperatively, i.e., order of declaratively; i.e., order of
Order
invocation matters. invocation does not matter.
Code vs. Methods are code, i.e., they Functions are code-as-data;
Data are invoked and executed. i.e., they are not only
executable, but are also
passed around like data.
14
element it represents, e.g. its pathname, whether it is a file or a directory,
which other elements it contains, etc. These fields are accessed by the
listFiles method.
The listFiles method takes a file filter as an argument. The file filter is a
function, here expressed as a lambda expression. It is passed to the
listFiles method which then applies it to all files and directories in
myDir. The example illustrates the code-as-data principle: the function (i.e.
the file filter lambda) is passed to an operation (i.e. the listFiles
method). It also illustrates the separation between "what" and "how": the
function describes "what" is supposed to be applied to all files and
directory and the receiving method controls "how" the filter is applied,
e.g. in which order. In addition, the example illustrates the principle of a
pure function: the file filter does not produce side effects; it just takes a
File and returns a boolean.
The file filter is again expressed as a lambda, but this time it produces side
effects, namely incrementing two counters.
After this excursion into the realm of programming language principles,
we know that lambda expressions in Java denote (pure or impure)
functions. As Java is an object-oriented and not a functional
programming language the question is: How are lambdas aka functions
integrated into the Java programming language? runtime representation
Representation of Lambda Expressions
15
the receiving side, i.e., in the operation that takes the lambda, it is used
like an object.
For illustration we will again use the previous example of the listFiles
method from class java.io.File. Below we see where the lambda
expression is defined and passed on as an argument to the listFiles
method.
File myDir = new File("\\user\\admin\\deploy");
if (myDir.isDirectory()) {
File[] files = myDir.listFiles(
(File f) -> { return f.isFile(); }
);
}
The listFiles method take a FileFilter object and invokes its accept
method.
The example demonstrates that a lambda expression has properties in
common with functions and objects, depending on the point of view.
Conceptually, a lambda expression is a function. It is an unnamed
piece of reusable functionality. It has a signature and a body, but no
name. It may or may not be pure, i.e., may or may not have side
effects. We pass it around as an argument to a method or as a return
value from a method.
When a lambda expression is passed as an argument to a method the
receiving method treats it like an object. In the example the lambda
expression (or more precisely, a reference to the lambda expression)
is received as the argument of the listFiles method. Inside the
listFiles method the lambda expression is a reference to an object
of a subtype of the FileFilter interface. This lambda object is a regular
object; e.g. it has an address and a type.
16
Basically, you think in terms of a function when you define a lambda
expression and in terms of a method when you use it. The lambda object
that links definition and use together is taken care of by the compiler and
the runtime system. We as users need not know much about it.
Practically all decisions regarding the representation and creation of the
lambda object (short for: the object that represents a lambda expression at
runtime) are made by the compiler and the runtime system. This is good,
because it enables optimizations under the hood that we as users neither
want to deal with nor care about. For instance, from the context in which
the lambda expression is defined the compiler captures all information
that is needed to create a synthetic type for a lambda object, but the
compiler does not yet create that type; it just prepares the type generation.
The actual creation of the lambda object's synthetic type and the creation
of the lambda object itself are done dynamically at runtime by the virtual
machine. For this dynamic creation of the synthetic lambda type and the
lambda object the invokedynamic byte code is used, which was added to
Java in release 7. Using these dynamic features it is possible to delay the
creation until first use, which is an optimization: if you just define, but
never use, the lambda expression then neither its type nor the lambda
object will ever be created. 6 type of lambda expression target type functional interface
Functional Interfaces
Part of the magic of tying the definition of a lambda expression to the use
of a lambda expression is inference of the type by which the lambda
expression is used. This type is called the target type. The synthetic type
that the runtime system dynamically creates, as mentioned above, is a
subtype of the target type.
In the previous file filter example we have seen that the target type of our
lambda expression is FileFilter. In the example we define a lambda
expression, pass it to the listFiles method, in which it is then used like
an object of a subtype of FileFilter. In a way, this is surprising. After
all, we did not specify that our lambda expression implements the
FileFilter interface. In fact, we did not say anything regarding the
lambda expression's type. Similarly, the receiving listFiles method never
indicated that it happily accepts lambda expressions. Instead it requires an
object of a subtype of FileFilter. How does it work?
6If you are interested in the details of the creation of the lambda object please read the
section on "Lambda Translation" in the Lambda Reference document or take a look at
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html.
17
The underlying magic that the compiler performs to make it happen is type
inference. The compiler takes a look at the context in which the lambda
expression is defined and figures out which type is required. Then it takes
a look at the lambda expression itself and figures out whether the lambda
is compatible to the required type (plus minus a couple of adjustments if
needed).
If Java were a functional language then the most natural type for a lambda
expression would be some kind of function type, which is a special category
of type that is reserved for description of functions. A function type
merely describes the signature and could for example look similar to
(int,int)->boolean if it is meant to denote the type of a function that
takes two int arguments and returns a boolean value.
Java is no functional language and traditionally had no such thing as a
function type. So, the language designers had the choice to add function
types as new category of type. Since they did not want to introduce major
changes to Java's type system they tried to find a way to integrate lambda
expressions into the language without resorting to function types. They
found a way to do it by using special interface types instead: these
interfaces as so-called functional interfaces, previously known as SAM types
(where SAM stands for Single Abstract Method).
A functional interface essentially is an interface with one method. 7
Countless such interfaces exist in the JDK already, some of them since its
first release. The interface Runnable is a classic example of a functional
interface. It demands the implementation of exactly one method:
void r un(). There are many more: Readable, Callable, Iterable,
Closeable, Flushable, Formattable, Comparable, Comparator, or the
FileFilter interface, which we have been using in the previous example.
FileFilter requires exactly one method and for this reason is a functional
interface type. Our lambda expression has a matching signature: it takes a
File argument, returns a boolean value and throws no checked exception.
Hence the compiler converts the lambda expression to the functional
interface type FileFilter. Inside the listFiles method the lambda is
then indistinguishable from an object of type FileFilter. Here is the
implementation of method File.listFiles:
public File[] listFiles(FileFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss) {
File f = new File(s, this);
if ((filter == null) || filter.accept(f))
files.add(f);
}
return files.toArray(new File[files.size()]);
}
Inside this method the lambda is bound to the filter argument. The
lambda's functionality is triggered when the functional interface's method
is invoked, i.e. when filter.accept() is called.
The conversion to a functional interface can have funny effects,
demonstrated in the following example:
Say, we have two functional interfaces:
public interface FileFilter { boolean accept(File pathname); }
and
public interface Predicate<T> { boolean test(T t); }
19
Our lambda is convertible to both functional interface types 8 :
FileFilter filter = (File f) -> { return f.isFile(); };
Predicate<File> predicate = (File f) -> { return f.isFile(); };
If, however, we try to assign the two resulting variables to each other the
compiler would flag it as an error, although the two variables represent
the same lambda expression. The reason is that the two variables to
which the lambda is bound are of two different, incompatible types.
Also, there might occasionally be situations in which the compiler cannot
figure out a matching functional interface type, like in this example:
Object ref = (File f) -> { return f.isFile(); };
8 Expressions that have different type depending on the context in which they appear are
called poly expression. The actual type of a poly expression is always inferred by the
compiler. Lambda expressions and method references are examples of poly expression.
Poly expressions also occur in conjunction with generic; for example instance creation
expressions that use a diamond <> like new ArrayList<>() are poly expressions.
9 Details regarding functional interface conversion can be found in the sections on "Target
Typing" and "Type Inference" in the Lambda Reference document. There you find details
regarding which context is suitable for functional interface conversion and how conversion
to generic functional interfaces works.
20
Syntax
Runtime Overhead
21
The bottom line is that lambda expressions have the potential for
optimizations and reduced runtime overhead compared to anonymous
inner classes. 10 variable binding
Variable Binding
void method() {
int cnt = 16;
10 Details regarding the translation of lambda expressions can be found in the sections on
Lambda expression:
void method() {
int cnt = 16;
Runnable r = () -> { int cnt = 0; // error: cnt has already been defined
System.out.println("cnt is: " + cnt);
};
...
}
Both the anonymous class and the lambda define a variable named cnt
while there already is a variable with this name defined in the enclosing
method. Since the anonymous class establishes a scope of it own it may
define a second cnt variable which is tied to the scope of the inner class.
The lambda, in contrast, is part of the enclosing scope and the compiler
23
considers the definition of a cnt variable in the lambda body a colliding
variable definition. In the scope of the enclosing context there already is a
definition of a cnt variable and the lambda must not define an additional
cnt variable of its own.
12 The iterator pattern is one of the so-called Gang of Four design patterns
(Gamma, Erich, Helm, Richard, Johnson, Ralph, & Vlissides, John. (1995).
Design patterns: Elements of reusable object-oriented software . Addison-Wesley.)
25
}
The for-each-loop internally uses an iterator and basically looks like this:
26
Given this forEach method the sequence's user only has to supply the
functionality that is to be applied to each element in the sequence.
private static void checkBalance(List<Account> accList) {
accList.forEach(
(Account a) -> { if (a.balance() < a.threshold)
a.alert();
}
);
}
Since Java 8 the JDK has a new abstraction named Stream<E> which is a
view to a collection and has bulk operations that perform internal
iteration. These bulk operations include forEach, filter, map, reduce, and
many more. Thanks to the internal iteration, streams support sequential
as well as parallel execution in a very convenient way. Here is an example:
List<Account> accountCol = … ;
accountCol
.stream()
.filter(a -> a.balance() > 1_000_000_00) // intermediate
27
.map(Account::balance) // intermediate
.forEach(b -> {System.out.format("%d\t",b);}); // terminal
28
Parallel execution:
List<Account> accountCol = … ;
accountCol
.parallelStream()
.filter( a -> a.balance() > 1_000_000_00 ) // intermediate
.map(Account::balance) // intermediate
.forEach(b -> {System.out.format("%d\t",b);}); // terminal
29
public interface Manager extends Employee {
}
public interface Department {
public enum Kind {SALES, DEVELOPMENT, ACCOUNTING,
HUMAN_RESOURCES}
public Department.Kind getKind();
public String getName();
public Manager getManager();
public Set<Employee> getEmployees();
}
public interface Corporation {
public Set<Department> getDepartments();
}
Imperative Approach
/*
* find all managers of all departments with an employee older
than 65
*/
Manager[] find(Corporation c) {
List<Manager> result = new ArrayList<>();
for (Department d : c.getDepartments()) {
for (Employee e : d.getEmployees()) {
if (e.getAge() > 65) {
resu lt.add(d.getManager());
}
}
}
return result.toArray(new Manager[0]);
}
Declarative Approach
/*
* find all managers of all departments with an employee older
than 65
30
*/
Manager[] find(Corporation c) {
return
c.getDepartments().stream() // 1
.filter(d -> d.getEmployees().stream() // 2
.map(Employee::getAge) // 3
.anyMatch(a -> a>65)) // 4
.map(Department::getManager) // 5
.toArray(Manager[]::new) // 6
}
In line //3, we use the map operation on the stream of employees. The
map operation needs a function that takes an element from the stream (an
Employee in that case) and returns another object of a potentially different
type (the age in our example). The mapper function is provided as the
method reference Employee::getAge. because the getAge method does
exactly what we need: it maps an employee to its age.
In line //4, we use the anyMatch operation of the stream of age values
return from the preceding map operation. The anyMatch operation takes
elements from the stream, applies a predicate to each element, and stops
as soon as an element is found for which the predicate returns true. We
supply the required predicate as another lambda that takes the age and
returns true if the age is greater than 65.
31
The result of the filter operation and the lengthy lambda expression in line
//2 to //4 is the stream of all departments with an employee older than
65.
In line //5, we map each department to its respective manager. As the
mapper function we use the method reference Department::getManager.
The result is the stream of all managers of all departments with an
employee older than 65.
So far, none of the lambdas or referenced methods have been executed
because filter and map are intermediate operations.
In line //6,
we convert the manager list into an array via the stream's
toArray method. The toArray method needs a generator, which takes a
size value and returns an array of the requested size. The generator in our
example is a constructor reference, namely the reference to the
constructor of manager arrays: Manager[]::new.
The two approaches look very different. The imperative style
intermingles the logic of iterating (the various nested loops) and the
application of functionality (retrieval of departments and employees,
evaluation of age values, collecting results). The declarative style, in
contrast, separates concerns. It describes which functionality is supposed
to be applied and leaves the details of how the functions are executed to
the various operations such as filter, map, anyMatch, and toArray. pipelining
Fluent Programming
32
Here is a brief illustration of the pipelining performed by the
implementation of streams:
String[] txt =
{"State","of","the","Lambda","Libraries","Edition"};
IntStream is = Arrays.stream(txt)
.filter(s -> s.length() > 3)
.map(s -> s.length())
.forEach(l -> System.out.println(l));
In the code snippet we take a string array, turn it into a stream, pick all
strings longer than 3 characters, and map them to their length.
The source code suggests that first the filter is applied to all elements in
the stream and then the mapper is applied to all elements in a second pass
over the stream. In reality, the chain of filter and map operation is
postponed until the terminal forEach operation is executed. The terminal
operation triggers only a single pass over the sequence during which both
the filter and the mapper are applied to each element.
Creating such a pipeline per element does not only reduce multiple passes
over the sequence to a single pass, but also allows further optimizations.
For instance, the length method need not be invoked twice per string; the
repeated call can be eliminated by the JIT compiler.
Which style looks more pleasant and understandable is to the eye of the
beholder. Certainly the declarative code looks alien to Java programmer
unfamiliar with the declarative or fluent programming. After getting
accustomed to the declarative style it will probably appear equally pleasant
and understandable. execute-around-method
33
Execute-Around-Method Pattern
So far, we have been exploring lambdas solely in the context of the JDK
collections and streams. However, lambda expressions are useful beyond
the JDK stream abstractions and internal iteration. They are convenient
and helpful in all places where operations take functionality as an
argument. One such occasion is the so-called Execute-Around-Method
pattern. It is a programming technique for eliminating code duplication.
Occasionally we encounter situations where it is required that some
boilerplate code must to be executed both before and after a method (or
more generally, before and after another piece of code that varies). Often
we simply duplicate the boilerplate code via copy-and-paste and insert the
variable functionality manually. Following the DRY (don't repeat
yourself) principle you might want to remove the code duplication via
copy-and-paste. For this purpose it is necessary to separate the boilerplate
code from the variable code. The boilerplate code can be expressed as a
method and the variable piece of code can be passed to this method as the
method's argument. This is an idiom where functionality (i.e. the variable
piece of code) is passed to a method (i.e. the boilerplate code). The
functionality can be conveniently and concisely be expressed by means of
lambda expressions.
Let us consider an example for the Execute-Around-Method pattern: use
of explicit locks. An explicit ReentrantLock (from package
java.util.lock) must be acquired and released before and after a critical
region of statements. The resulting boilerplate code looks like this:
class SomeClass {
private ... some data ...
private Lock lock = new ReentrantLock();
...
public void someMethod() {
lock.lock();
try {
... critical region ...
} finally {
lock.unlock();
}
}
}
In all places where we need to acquire and release the lock the same
boilerplate code of "lock-try-finally-unlock" appears. Following the
Execute-Around-Method pattern we would factor out the boilerplate code
into a helper method:
class Utilities {
public static void withLock(Lock lock, CriticalRegion cr) {
34
lock.lock();
try {
cr.apply();
} finally {
lock.unlock();
}
}
}
...
}
35
clause. The exception raised in the critical region of the pop method does
not cause any serious trouble; it is unchecked and need not be declared in
a throws clause. The lambda that we need for implementing the pop
method can throw it anyway. The return type, in contrast, causes trouble.
In order to allow for lambda expressions with a return type different from
void we need an additional CriticalRegion interface with an apply
method that returns a result. This way we end up with two interfaces:
@FunctionalInterface
public interface VoidCriticalRegion {
void apply();
}
@FunctionalInterface
public interface IntCriticalRegion {
int apply();
}
Given the additional helper method and functional interface the pop
method can be implemented like this:
private class MyIntStack {
...
36
In analogy to the return type you might wonder whether we need
additional functional interfaces for critical regions with different argument
lists. It turns out that arguments to the critical region are usually not an
issue. The lambda expression, which implements the critical region
interface, can access (implicitly) final variable of the enclosing context.
Consider the push method again; it takes an argument.
private class MyIntStack {
private Lock lock = new ReentrantLock();
private int[] array = new int[16];
private int sp = -1;
...
}
The critical region lambda accesses the push method's argument, which is
either explicitly declared final or implicitly treated as such. As long as the
critical region only reads the argument and does not modify it there is no
need for additional helper methods or functional interfaces.
Wrap-Up
37
Default Methods
default method
Lambda expressions and method references are not the only features that
have been to the language in release 8 of Java. Java 8 also supports a
novel feature named default methods. In principle, default methods are
entirely unrelated to lambda expressions. It is just that they are the other
new language feature in Java 8. Both lambda expressions and default
methods are part of the Java Specification Request JSR 335 13 and for this
reason we mention them in this tutorial. interface evolution
Interface Evolution
Default methods are needed for interface evolution. From the previous
sections on streams and bulk operation we know that the JDK has been
radically overhauled in Java 8. Reengineering such an existing framework
often involves the modification of the framework's interfaces. As we all
know, modifying an interface breaks all classes that implement the
interface. In other words, changing any of the interfaces in the JDK
collection framework would break millions of lines of code. This is
clearly not a viable option for a reengineering effort of the JDK. Hence
the JDK implementers had to figure a means of extending interfaces in a
backward compatible way and they invented default methods.
Default methods can be added to an interface without breaking the
implementing classes because default methods have an implementation.
If every additional method in an interface comes with an implementation
then no implementing class is adversely affected. Instead of providing
their own implementations of additional methods, the implementing
classes can simply inherit the implementations offered by the interface's
default methods. An implementing class may choose to override the
default implementation suggested by the interface. For this reason, the
default methods were initially called virtual extension methods; they can be
overridden like virtual methods inherited from a superclass.
Let us consider an example. As mentioned earlier, the JDK collections
have been extended for Java 8 and one of the changes is addition of a
forEach method to all collection in the JDK. Hence the JDK designers
wanted to add the forEach method to the Iterable interface, which is the
topmost interface of all collections in the JDK.
http://openjdk.java.net/projects/lambda/.
38
If this addition is made the traditional way (without default methods), the
extended Iterable interface looks like this:
public interface Iterable<T> {
public Iterator<T> iterator();
public void forEach(Consumer<? super T> consumer);
}
In such a situation the compiler cannot resolve the ambiguity and reports
an error. In order to enable the programmer to resolve the ambiguity
there is syntax for explicitly stating which method class C is supposed to
inherit. A resolution could look like this:
class C implements I1, I2 {
public void foo() { I1.super.foo(); }
}
40
reports an error (like in the example above) there is syntax for explicit
resolution. If you are interested in a more elaborate discussion of multiple
inheritance in Java and/or details regarding the resolution of ambiguous
multiple inheritance of default methods, please consult the section on
"Default Methods" in the Lambda Reference document.
41
Reference to Related Reading
Documentation & Specification
Lambda Expressions - Reference
This tutorial aims to provide a first glance at the language features that
were added to the Java programming language in release 8 of Java, namely
lambda expression, method reference, and default methods.
Comprehensive coverage of the details can be found in the "Lambda
Reference".
URL: to be provided
Conference Presentations
Brian Goetz: The Road to Lambda, JavaOne 2012
https://oracleus.activeevents.com/connect/sessionDetail.ww?SESSION_
ID=4862
42
Brian Goetz: Lambda: A Peek Under the Hood, JavaOne 2012
https://oracleus.activeevents.com/connect/sessionDetail.ww?SESSION_
ID=6080
Tool support
EAP versions of Intellij IDEA
provide amazingly good support for lambda expressions and other parts
of the Java 8 feature set.
http://confluence.jetbrains.com/display/IDEADEV/IDEA+12+EAP
43
Nightly builds of NetBeans 8
provide experimental lambda support.
http://bertram2.netbeans.org:8080/job/jdk8lambda/lastSuccessfulBuild/
artifact/nbbuild/
Miscellaneous
Angelika Langer & Klaus Kreft, The Closure Debate, June 2008
An overview of the debate that led to the development of lambda
expressions for Java.
http://www.javaworld.com/javaworld/jw-06-2008/jw-06-closures.html
for an overview
44
Appendix
Source Code of Fluent Programming Case Study
45
Declarative & Sequential
/*
* find all managers of all departments with an employee
* older than 65
*/
Manager[] find(Corporation c) {
return
c.getDepartments().stream() // 1
.filter(d -> d.getEmployees().stream() // 2
.map(Employee::getAge) // 3
.anyMatch(a -> a>65)) // 4
.map(Department::getManager) // 5
.collect(Collectors.toList()) // 6
.toArray(new Manager[0]); // 7
}
46
private final int targetBatchSize = 2;
47
Source Code of Execute-Around-Method Pattern
Case Study
48
IntStack Class
public class IntStack {
private Lock lock = new ReentrantLock();
private int[] array = new int[16];
private int sp = -1;
49
Index
interface evolution · 39
@ internal iteration · 26, 27
@FunctionalInterface · 19 iteration
A external · 26, 27
ad-hoc functionality · 9 internal · 26, 27
ambiguous inheritance · 41 L
anonymous inner class · 9, 11, 14 lambda · see lambda expression
vs. lambda expression · 9, 21 calculus · 9
anonymous method · 9 lambda expression · 9
B representation · 16, 22, see runtime
binding · see variable binding representation
bulk operation syntax · 21
intermediate · see translation · see runtime
terminal · see terminal stream representation
operation vs. anonymous inner class · 9, 21
bulk operations · 25 lambda object · 17
parallel · 25 lexical scope · 24
C M
closure · see lambda expression map() · 29
closure debate · 8 meaning of this/super · see lexical
collection framework extension · see scope
bulk operations method · 12, 14, 15
difference to function · 12
D vs. function · 12, 17
declarative programming · 13, 14, 31 method & function
default method · 39 difference · 12
E method reference · 11
effectively final variable · 23 multicore hardware · 8
execute-around-method pattern · 34 multiple inheritance · 41
external iteration · 26, 27 ambiguous · 41
F O
filter() · 29 object-oriented programming · 13
filter-map-reduce · see bulk operations P
fluent programming · 30, 33 parallel stream · 30
forEach() · 29 pipeline · 33
function · 12, 15 poly expression · 20
difference to method · 12 procedural programming ·
impure · 15 programming language
pure · 13, 14 functional · see functional
vs. method · 12, 17 programming
functional interface · 18 imperative · see imperative
annotation · see programming
@FunctionalInterface object-oriented · see object-
functional programming · 12, 13 oriented programming
I procedural · see procedural
imperative programming · 13, 14, 31 programming
50
pure functional · see functional
programming
T
pure function · 13, 14 target type · 18
translation strategy · see runtime
R representation
runtime representation · 22, see type inference · 18
lambda expression, representation type of lambda expression · see target
type
S
SAM type · see functional interface V
scope · 24 variable
lexical · 24 binding · 22
stream · 26 capture · 23
parallel · 30 effectively final · 23
sequential · 28 variable binding · 22
stream operation variable capture · 22
parallel · 29 virtual extension method · see default
streams · 28 method
51