Goals
4
• Understand the streams API• Able to use them in everyday Java• Know when to hold ‘em/fold ‘em
Hot Streaming Java
Objectives
5
• Review the functional paradigm• Present stream operations• Examine stream-based algorithms• Compare imperative algorithms vs.
serial & parallel streams
Hot Streaming Java
Streams
7
• Introduced as part of Java 8• Minimal syntactical changes in Java 8• Streams are as functional as Java gets
Hot Streaming Java
What’s a stream?
8
“A declarative construct used to express an algorithm as a series of operations working
on a stream of data”
Hot Streaming Java
Streams
11Hot Streaming Java
• Streams are functional constructs:• Functions as first-class citizens• Emphasis on immutability/
statelessness• Avoidance of side-effects• Declarative programming
Lambda refresh
13Hot Streaming Java
Lambda expressionsParameter name -> Lambda expression
i -> System.out.println(i);
i -> i * 2;
Function <T, R>R apply(T t);
Supplier<T>T get()
Functional Interfaces
14Hot Streaming Java
Functional
Interfaces
Consumer
Function
Predicate
Supplier
Consumer<T> void accept(T t);
Predicate<T>boolean test(T t);
Functional Interfaces
15Hot Streaming Java
BiFunction<Integer, Integer, Integer>
multiply = (x, y) -> x * y;
IntBinaryOperator multiply2 = (x, y) -> x * y;
IntBinaryOperator multiply3 = Math::multiplyExact;
int product = multiply.apply(10, 20);
int product2 = multiply2.applyAsInt(10, 20);
int product3 = multiply3.applyAsInt(10, 20);
16Hot Streaming Java
Streams
Lambda Lambda Lambda Lambda
Operation 1
Operation 2
Operation 3
Operation 4
• Operations fused into pipelines• Customized with lambdas• Abstract for/while loop• Fluent-style programming
17Hot Streaming Java
Streams
List<Integer> integersIn =
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> evenIntegers = new ArrayList<>();
integersIn.stream().
filter(x -> x % 2 == 0).
forEach(evenIntegers::add);
parallel().
18Hot Streaming Java
Constructing streams
A coherent stream
1 build + 0..n intermediate + 1 terminal
19Hot Streaming Java
Constructing streams
Good stream
list.stream().filter(x-> x ^ 2 == 0).reduce((l, r) -> l + r);
Build operation Intermediate operation Terminal operation
20Hot Streaming Java
Constructing streams
Bad stream!
list.stream().filter(x-> x ^ 2 == 0);
No terminal operation
21Hot Streaming Java
Constructing streams
Badder stream!!
Stream.iterate(0, i -> ++i).anyMatch(i -> i < 0);
Infinite stream
22Hot Streaming Java
Constructing streams
Baddest stream!!!
List<String> words = new ArrayList<>(
Arrays.asList(“This”, “sentence”, “contains”, “five”, “words”));
// This stream has interference – avoid!
words.stream().
forEach(s -> {if (s.equals(“five”)) words.add(“thousand”);});
Lambda interferes with stream, has side-effects and is not thread-safe
23Hot Streaming Java
Constructing streams
Generic vs. specialized
//Generic
Stream.iterate(1, i -> ++i).
limit(10).reduce((l, r) -> l + r);
//Specialized
IntStream.rangeClosed(1, 10).sum();
Stream operations
25Hot Streaming Java
Stream
operations
Build
Filter
Map
Reduce
Iterate
Peek
• Create the stream• Collections:
List.stream()
• Buffered readers: BufferedReader.lines()
• Or thin-air:IntStream.range(1, 10)
Stream operations
26Hot Streaming Java
Stream
operations
Build
Filter
Map
Reduce
Iterate
Peek
• Allows or blocks data• If-statements• True:
• let the stream element thru
• False: • block the element
Stream operations
27Hot Streaming Java
Stream
operations
Build
Filter
Map
Reduce
Iterate
Peek
• Transforms stream data• Same or different type
• map, mapToInt, mapToLong
• Same or different cardinality• flatMap, flatMapToInt, flatMapToLong
Stream operations
28Hot Streaming Java
Stream
operations
Build
Filter
Map
Reduce
Iterate
Peek
• Boils down to one thing• Ordinary reduction:
(((n1 + n2) + n3) + n4) + n5
(((1 + 2) + 3) + 4) + 5((3 + 3) + 4) + 5
(6 + 4) + 510 + 5
15
• Mutable reduction:• Mutate an object while collecting
Left fold
30Hot Streaming Java
Stream grep –cImperative algorithm
private static long grepDashCImperative(BufferedReader in,
String upperCaseSearchWord) {
String nextLine = in.readLine();
int count = 0;
while (nextLine != null) {
String upperCaseLine = nextLine.toUpperCase();
if (upperCaseLine.contains(upperCaseSearchWord)) {
count++;
}
nextLine = in.readLine();
}
return count;
}
Imperative style
31Hot Streaming Java
Stream grep –cUsing forEach
private static long grepDashC(BufferedReader in,
String upperCaseSearchWord) {
int count = 0;
in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
forEach(next -> count++);
return count;
}
Stream style
32Hot Streaming Java
Stream grep –cUsing forEach
private static long grepDashC(BufferedReader in,
String upperCaseSearchWord) {
int count = 0;
in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
forEach(next -> count++);
return count;
} Cannot mutate a local variable in a lambda
33Hot Streaming Java
Stream grep –cUsing reduce
private static long grepDashC(BufferedReader in,
String upperCaseSearchWord) {
// The long way
return in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
mapToLong(count -> 1).
reduce(0, (l, r) -> l + r);
}
Using the reduce
operation
34Hot Streaming Java
Stream grep –cUsing count()
private static long grepDashC(BufferedReader in,
String upperCaseSearchWord) {
// Using the built-in function count
return in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
count();
}
Using the count
operation
36Hot Streaming Java
Stream grepForeach-based accumulation
private static String grep(BufferedReader in,
String upperCaseSearchWord) {
StringBuilder accumulator = new StringBuilder();
// Accumulate the strings via foreach
in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
forEach(accumulator::append);
return accumulator.toString();
}
forEach is not a reduction tool
37Hot Streaming Java
Stream grepReduction-based accumulation
private static String grep(BufferedReader in,
String upperCaseSearchWord) {
// Accumulate the strings via reduction
return in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
reduce("", (l, r) -> l.concat(r).concat(", "));
}
Reduce is not the right tool for the job:String is being copied every time
38Hot Streaming Java
Stream grepMutable reduction
private static List<String> grep(BufferedReader in,
String upperCaseSearchWord){
// Accumulate the strings via collect
return in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
collect(ArrayList<String>::new,
ArrayList<String>::add,
ArrayList<String>::addAll);
}
Collect is a mutable reduction operation
Collect
Supplier(Supplier)
Creates the mutable structure
Accumulator(BiConsumer)
Adds to the structure
Combiner(BiConsumer)
Merges each partial
accumulation
ArrayList<String>::add
ArrayList<String>::addAllArrayList<String>::new
40Hot Streaming Java
Stream grepMutable reduction
private static List<String> grep(BufferedReader in,
String upperCaseSearchWord){
// Accumulate the strings via collect
return in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
collect(ArrayList<String>::new,
ArrayList<String>::add,
ArrayList<String>::addAll);
}
Scary syntax can be replaced by…
41Hot Streaming Java
Stream grepMutable reduction
private static List<String> grep(BufferedReader in,
String upperCaseSearchWord){
// Accumulate the strings via collect with collectors
return in.lines().
map(String::toUpperCase).
filter(s -> s.contains(upperCaseSearchWord)).
collect(Collectors.toList());
}
Declarative collection
Rolling your own Collector
42Hot Streaming Java
• Implement Collector interface• Supplier, Accumulator, Combiner, Finisher
• Set characteristics• Concurrent: accumulation can be concurrent• Unordered: Collection is not ordered• Identity finish: Turn on/off finishing
43Hot Streaming Java
Stream grepParallel streaming
private static List<String> grep(BufferedReader in,
String upperCaseSearchWord){
// Accumulate the strings via collect with collectors
return in.lines().
map(String::toUpperCase).parallel().
filter(s -> s.contains(upperCaseSearchWord)).
collect(Collectors.toList());
}
Stream thinking
47
• Thinking in streams can be difficult• Steep learning curve
• Lambdas• Method references• Standard functional interfaces• Generics
• Requires a functional mindset• Look at algorithms differently• Not all algorithms translate to streams
Hot Streaming Java
Stream thinking
Streams are best suited for algorithms
that… Traverse a sequence of data
Reduces sequence to a thing
No read-ahead no read-behind
No state change during traversal
Stream thinking
49
• When algorithms don’t fit:• Re-think the algorithm functionally• Use stream operations to maintain state• Or don’t use streams!
Hot Streaming Java
52Hot Streaming Java
public static ArrayList<Integer> imperativeQuickSort(ArrayList<Integer> array, int low, int n) {int lo = low;int hi = n;
if (lo >= n) return array;
// Step 1: find pivot pointint mid = array.get((lo + hi) / 2);
// Step 2: find & swap values less & greater than pivotwhile (lo < hi) {
while (lo < hi && array.get(lo) < mid) {lo++;
}while (lo < hi && array.get(hi) > mid) {
hi--;}if (lo < hi) {
// Swap valuesint temp = array.get(lo);array.set(lo, array.get(hi));array.set(hi, temp);lo++;hi--;
}}
if (hi < lo) lo = hi;
// Steps 3: split the array and repeat recursivelyimperativeQuickSort(array, low, lo);imperativeQuickSort(array, lo == low ? lo + 1 : lo, n);
Imperative quick sort
public static List<Integer> functionalSort(List<Integer> array) {
List<Integer> returnArray = array;
if (array.size() > 1) {
// Step 1
int mid = array.get(array.size() / 2);
// Step 2
Map<Integer, List<Integer>> map = array.stream().parallel().
collect(Collectors.groupingBy(i -> i < mid ? 0 : i == mid ? 1 : 2));
// Step 3
List<Integer> left = functionalSort(map.getOrDefault(0, new ArrayList<>()));
List<Integer> middle = map.getOrDefault(1, new ArrayList<>());
List<Integer> right = functionalSort(map.getOrDefault(2, new ArrayList<>()));
left.addAll(middle);
left.addAll(right);
returnArray = left;
}
return returnArray;
}
53Hot Streaming Java
Functional quick sort
55Hot Streaming Java
public static boolean[][] getNextGeneration(boolean[][] oldGeneration)
{
boolean[][] newGeneration = new boolean[oldGeneration.length][oldGeneration.length];
for (int y = 0; y < oldGeneration.length; ++y) {
for (int x = 0; x < oldGeneration.length; ++x) {
newGeneration[y][x] = isAlive(oldGeneration, y, x);
}
}
return newGeneration;
}
private static int countLiveNeighborCells(boolean[][] generation, int y, int x)
{
int count = 0;
for (int yIndex = y - 1; yIndex <= y + 1; ++yIndex) {
if (yIndex >= 0 && yIndex < generation.length) {
for (int xIndex = x - 1; xIndex <= x + 1; ++xIndex) {
if (xIndex >= 0 && xIndex < generation.length) {
if (generation[yIndex][xIndex] && !(xIndex == x && yIndex == y)) {
++count;
}
}
}
}
}
return count;
}
private static boolean isAlive(boolean[][] generation, int y, int x)
{
int liveCells = countLiveNeighborCells(generation, y, x);
return (generation[y][x] && liveCells >= 2 && liveCells <= 3) ||
(!generation[y][x] && liveCells == 3);
}
Imp
erat
ive
56Hot Streaming Java
Conway’s Game of Lifepublic static boolean[][] getNextGeneration(boolean[][] oldGeneration)
{
boolean[][] newGeneration = new boolean[oldGeneration.length][oldGeneration.length];
for (int y = 0; y < oldGeneration.length; ++y) {
for (int x = 0; x < oldGeneration.length; ++x) {
newGeneration[y][x] = isAlive(oldGeneration, y, x);
}
}
return newGeneration;
}
private static int countLiveNeighborCells(boolean[][] generation, int y, int x)
{
int count = 0;
for (int yIndex = y - 1; yIndex <= y + 1; ++yIndex) {
if (yIndex >= 0 && yIndex < generation.length) {
for (int xIndex = x - 1; xIndex <= x + 1; ++xIndex) {
if (xIndex >= 0 && xIndex < generation.length) {
if (generation[yIndex][xIndex] && !(xIndex == x && yIndex == y)) {
++count;
}
}
}
}
}
return count;
}
private static boolean isAlive(boolean[][] generation, int y, int x)
{
int liveCells = countLiveNeighborCells(generation, y, x);
return (generation[y][x] && liveCells >= 2 && liveCells <= 3) ||
(!generation[y][x] && liveCells == 3);
}
public static boolean[][] getNextGenerationFunctional(boolean[][] oldGeneration)
{
boolean[][] newGeneration = new boolean[oldGeneration.length][oldGeneration.length];
List<Coordinates> list = new ArrayList<>();
IntStream.range(0, oldGeneration.length).
forEach(nextY ->
{
list.addAll(IntStream.range(0, oldGeneration.length).
parallel().
mapToObj(nextX -> new Coordinates(nextX, nextY)).
filter(coordinates -> isAlive(oldGeneration, coordinates.getY(),
coordinates.getX())).
collect(Collectors.toList()));
});
list.parallelStream().
forEach(nextCoordinate ->
newGeneration[nextCoordinate.getY()][nextCoordinate.getX()] = true);
return newGeneration;
}
private static int countLiveNeighborCells(boolean[][] generation, int y, int x)
{
return IntStream.rangeClosed(y - 1, y + 1).
filter(yInner -> yInner >= 0 && yInner < generation.length).
reduce(0, (yLeft, yRight) -> yLeft +
IntStream.rangeClosed(x - 1, x + 1).
filter(xInner -> xInner >= 0 && xInner < generation.length &&
generation[yRight][xInner] && !(xInner == x && yRight == y)).
reduce(0, (xLeft, xRight) -> xLeft + 1));
}
Stre
am-b
ased
Takeaway
Grep & Quick Sort offered compelling reasons to implement with Streams
Conway’s Game of Life did not
Parallel streams
Parallel streams are best suited for
algorithms that… Can split data easily & quickly
Have noorder
constraint
Can reduce concurrently
Have large N and/or Q
Hot Streaming Java
60Hot Streaming Java
Parallel streaming
Remember: A parallel stream must do everything a serial one does and more
61Hot Streaming Java
Parallel streaming
Sometimes, parallel streams are slower than their serial counterparts…
And also their imperative counterparts
Streams recap
Hot Streaming Java
5
Think functionally when using streams
4
3
2
1
Use the right reduction tool
Break through the learning curve
Use parallel streaming intelligently
Adapt thinking for streams or don’t use ‘em
Top Related