4. 4
Background
Before Java 8, there were anonymous classes:
JButton btn = new JButton("Click");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Button clicked!");
}
});
5. 5
Lambda Expressions
It is cumbersome to write anonymous classes in
many cases. Thus, lambda expressions are
introduced:
JButton btn = new JButton("Click");
btn.addActionListener(
evt -> System.out.println("Button clicked!"));
6. 6
Lambda Syntax
●
No arguments:
●
One argument:
●
Two arguments:
●
With explicit argument types:
●
Multiple statements:
() -> System.out.println("Hello")
s -> System.out.println(s)
(x, y) -> x + y
(Integer x, Integer y) -> x + y
(x, y) -> {
System.out.println(x);
System.out.println(y);
return (x + y);
}
7. 7
Lambda Syntax Sugar 1/2
Method reference
public class HashFunction {
public static int compute1(Object obj) { /*...*/ }
public static int compute2(int a, int b) { /*...*/ }
}
HashFunc::compute1
(x) -> HashFunc::compute1(x)
HashFunc::compute2
(x, y) -> HashFunc::compute2(x, y)
1
2
8. 8
Lambda Syntax Sugar 2/2
Constructor reference
String::new () -> new String()
(x) -> new String(x)
(x, y) -> new String(x, y)
(x, y, z) -> new String(x, y, z)
int[]::new (s) -> new int[s]
1
2
9. 9
Lambda Expression vs. Closure
●
Lambda expression
– A piece of the source code have the semantics like
anonymous classes.
– Evaluated to a closure object in the run-time.
●
Closure
– The run-time object that contains the captured
variables and an associated method.
10. 10
Capture Variable 1/3
●
We can refer to local variables in lambda
expressions.
– The referred local variables should be either final
or effectively final.
●
Evaluation of lambda expression will copy the
referred local variables to closure objects.
11. 11
Capture Variable 2/3
import java.util.stream.*;
import java.util.function.*;
public class CaptureExample {
public static IntUnaryOperator addBy(int n) {
return (x) -> x + n;
}
public static void main(String[] args) {
IntStream.of(1, 2, 3).map(addBy(1))
.map(addBy(2))
.forEach(System.out::println);
}
}
Capture variable11
2 The lambda expression is evaluated twice. Thus, two
closure objects are returned.
12. 12
Capture Variable 3/3
import java.util.function.*;
public class NonEffectivelyFinalExample {
public static IntUnaryOperator foo(int a, int n) {
if (a < 0) {
return (x) -> x + n;
}
n -= a;
return (x) -> x + n;
}
}
NonEffectivelyFinalExample.java:5: error: local variables referenced from
a lambda expression must be final or effectively final
return (x) -> x + n;
^
NonEffectivelyFinalExample.java:8: error: local variables referenced from
a lambda expression must be final or effectively final
return (x) -> x + n;
^
2 errors
Not effectively final
13. 13
Type Inference
What is the type for following lambda
expression?
Ans: It depends on the context. Java compiler
will infer the type automatically.
(x) -> System.out.println(x)
IntConsumer f = (x) -> System.out.println(x);
// (int) -> void
DoubleConsumer g = (x) -> System.out.println(x);
// (double) -> void
16. 16
Big Picture
How to Use Lambdas?
●
Stream – Create a stream from the collections,
arrays, etc.
●
Lazy operations – Operations that will be applied
to stream elements on demand.
●
Eager operations – Operations that will trigger
the evaluations.
– Enumerators, Collectors, etc.
17. 17
Stream
●
Stream is an abstraction of the underlying
data structure.
●
Isolates the data processing and control flow.
●
Abstracts the intermediate data structures.
●
Abstracts the lazy evaluation of operations.
18. 18
How to Create Stream?
●
From programmer-specified elements
●
From arrays
●
From java.util.Collection
●
From java.util.Map
java.util.Stream.of(...)
java.util.Arrays.stream(array)
java.util.Collection.stream()
java.util.Collection.parallelStream()
java.util.Map.entrySet().stream()
java.util.Map.values().stream()
21. 21
filter() 1/2
●
filter() takes a predicate function
●
If the predicate function returns false, then
the element will be removed from the
stream.
1 2 3 4 5
1 3 5
Input Stream
Output Stream
…
…
filter(x -> x % 2 != 0)
23. 23
map() 1/2
●
map() takes an unary function which will
convert one element to another element.
1 3 5Input Stream
Output Stream
…
…S:0 S:1 S:2
map(x -> "S:" + x / 2)
24. 24
import java.util.stream.*;
public class MapExample {
public static void main(String[] args) {
Stream.of(1, 3, 5)
.map(x -> "S:" + x / 2)
.forEach(System.out::println);
}
}
map() 2/2
1 3 5Input Stream
Output Stream
…
…S:0 S:1 S:2
map(x -> "S:" + x / 2)
Integer
String
25. 25
flatMap() 1/4
●
flatMap() takes an unary function.
●
The unary function will take an element and
return a stream.
●
flatMap() will concatenate these streams.
1 2 3Input Stream
Output Stream
…
…0 0 1 0 1 2
flatMap(x -> IntStream.range(0, x).boxed())
28. 28
flatMap() 4/4
import java.util.*;
import java.util.stream.*;
public class FlatMapExample2 {
public static void main(String[] args) {
HashMap<String, String[]> m =
new HashMap<String, String[]>();
m.put("A", new String[]{"a"});
m.put("B", new String[]{"b", "bb", "bbb"});
m.put("C", new String[]{"c", "cc"});
m.entrySet().stream()
.flatMap(e -> Arrays.stream(e.getValue()))
.forEach(System.out::println);
}
}
29. 29
peek() 1/3
●
peek() is the debug routine.
– e.g. Print the value during the lazy evaluation.
●
It will call the closure method with an element.
– You can print the element.
– You can manipulate the object (but not
recommended.)
31. 31
peek() 3/3
import java.util.stream.*;
class Num {
private int value;
public Num(int i) { value = i; }
public int get() { return value; }
public void set(int i) { value = i; }
};
public class PeekExample2 {
public static void main(String[] args) {
Stream.of(new Num(1), new Num(2), new Num(3))
.peek(x -> x.set(5))
.forEach(x -> System.out.println(x.get()));
}
};
5
5
5
Output
32. 32
Remarks 1/2
●
Lazy operations won't be executed until they
are triggered by an eager operation.
– This example will not print any numbers.
– The lambda expression is evaluated.
– The method in the closure is NOT invoked.
Stream.of(1, 2, 3).filter(x -> {
System.out.println(x);
return true;
});
33. 33
Remarks 2/2
●
It is suggested to use pure functions (i.e. no
side-effects) for these operations.
– It is hard to predict the behavior when we have
both mutability and lazy evaluation.
– Mutable + Lazy Evaluation = Disaster
– Except: For debug purpose only.
35. 35
forEach()
forEach() will iterate through the stream and
invoke the closure method with each element.
Stream.of(1, 2, 3, 4, 5)
.forEach(x -> System.out.println("GOT:" + x));
36. 36
count()
count() counts the number of elements in a
stream.
import java.util.stream.*;
public class CountExample {
public static boolean isPrime(int i) {
if (i > 2 && i % 2 == 0) return false;
for (int p = 3; p * p <= i; p += 2)
if (i % p == 0) return false;
return true;
}
public static void main(String[] args) {
long num = IntStream.range(2, 100).filter(Count::isPrime).count();
System.out.println("Num primes in [2,100) = " + num);
}
}
37. 37
toArray()
toArray() collects all of the elements from a
stream and put them in a java.lang.Object
array. import java.util.stream.*;
public class ToArrayExample {
public static void main(String[] args) {
Object[] array = Stream.of("apple", "bear", "bat", "cat")
.filter(x -> x.startsWith("b")).toArray();
for (Object s : array) {
System.out.println((String)s);
}
}
}
38. 38
reduce() 1/4
●
There are three overloaded methods
●
Accumulator should be associative.
1
2
3
Optional<T> reduce(BinaryOperator<T> acc)
T reduce(T id, BinaryOperator<T> acc)
<U> U reduce(U id, BiFunction<U, ? super T, U> acc,
BinaryOperator<U> combiner)
39. 39
reduce() 2/4
import java.util.Optional;
import java.util.stream.*;
public class ReduceExample1 {
public static Optional<Integer> sum(Stream<Integer> s) {
return s.reduce((x, y) -> x + y);
}
public static void main(String[] args) {
System.out.println(sum(Stream.of()));
System.out.println(sum(Stream.of(1)));
System.out.println(sum(Stream.of(1, 2)));
}
}
Output
Optional.empty
Optional[1]
Optional[3]
40. 40
reduce() 3/4
import java.util.Optional;
import java.util.stream.*;
public class ReduceExample2 {
public static int sum(Stream<Integer> s) {
return s.reduce(0, (x, y) -> x + y);
}
public static void main(String[] args) {
System.out.println(sum(Stream.of()));
System.out.println(sum(Stream.of(1)));
System.out.println(sum(Stream.of(1, 2)));
}
}
Output
0
1
3
41. 41
collect()
●
collect() takes a collector object and send
each element to the collector.
●
It allows the run-time to build the final data
structure gradually.
48. 48
Specialized Types
●
To reduce box/unbox overhead, several
specialized types for int, long, and double are
provided.
– Streams
– Function Interfaces
– Predicate
– Collector
49. 49
Specialized Stream
●
3 specialized (unboxed) streams:
– DoubleStream, IntStream, and LongStream
●
Box streams (convert from TypeStream to Stream<T>)
– Call IntStream.boxed() method.
●
Unbox streams (convert from Stream<T> to IntStream)
– Call Stream<T>.mapToInt() method.
50. 50
Specialized Functions
●
One argument and one return value
– Function<T, R>
– IntFunction<R>
– ToIntFunction<T>
– LongToIntFunction
– UnaryOperator<T>
– IntUnaryOperator
R apply(T t)
R apply(int t)
int applyAsInt(T t)
R apply(R t)
int applyAsInt(int t)
int applyAsInt(long t)
51. 51
Specialized Functions
●
Two arguments and one return value
– BiFunction<T, U, R>
– ToIntBiFunction<T, U>
– BinaryOperator<T>
– IntBinaryOperator
R apply(T t, U u)
int applyAsInt(T t, U u)
R apply(T t, U u)
int applyAsInt(int t, int u)
56. 56
Parallelism
●
If we limit ourselves to this restricted
programming style, then we can (almost) get
parallelism for free.
●
Call parallel() to convert a stream to
parallel stream.
57. 57
Parallelism Example
public class ParallelExample {
public static long sumSeq(long[] array) {
return Arrays.stream(array).sum();
}
public static long sumPar(long[] array) {
return Arrays.stream(array).parallel().sum();
}
public static void main(String[] args) {
int size = 40000000;
long[] array = new long[size];
for (int i = 0; i < size; ++i) array[i] = i;
long startSeq = System.nanoTime();
long resSeq = sumSeq(array);
long durationSeq = System.nanoTime() - startSeq;
long startPar = System.nanoTime();
long resPar = sumPar(array);
long durationPar = System.nanoTime() - startPar;
}
}
Output
resultSeq: 799999980000000
resultPar: 799999980000000
durationSeq: 58598502
durationPar: 21206305
60. 60
How to Receive Lambdas?
●
The closure object is an object that can be passed around.
●
The closure class will implement the specified interface.
●
To run the body of the lambda expression, invoke the
corresponding method.
●
The interface should contain exactly one non-default
abstract method.
– Because we can choose arbitrary method name.
61. 61
Functional Interfaces
●
Interfaces with one non-default method.
●
It is suggested to annotate with:
@FunctionalInterface.
@FunctionalInterface
public interface MouseEventListener {
public void accept(MouseEvent evt);
}
panel.registerListener(
evt -> System.out.println("Processed!"));
62. 62
Functional Interfaces
import java.util.HashSet;
public class Panel {
private HashSet<MouseEventListener> listeners =
new HashSet<MouseEventListener>();
public void registerListener(MouseEventListener cb) {
listeners.add(cb);
}
public void notifyListener(MouseEvent evt) {
listeners.forEach(x -> x.accept(evt));
}
}
63. 63
Default Method
●
Problem: The language designer would like to
add functions to interfaces, but it will break
the existing program.
●
Solution: We can specify the default method in
an interface.
– Default methods should only use other abstract
methods, i.e. no field access is allowed.
64. 64
Default Method
●
Default methods will be inherited (and may be
overrided) when the interface is extended.
●
The closest default method will be chosen.
●
To invoke the super default method, use the
super keyword.
●
It is necessary to override one if there are conflicts.
65. 65
Static Method
●
In Java 8, we can write static methods in
interfaces as well.
●
For example, Stream.of() is a notable static
method in the interface.