The new Java 8 stream library is the most exciting addition to come to Java in a long time. It allows entire algorithms to be expressed in one line, parallelism to be obtained on-demand, and plumbing code to be flushed down the drain. This presentation will show you how to think in streams, effective parallelization, plus advanced concepts like mutable reduction and declarative collection. Write better code with streams. This presentation will show you how.
7. Streams
7
⢠Introduced as part of Java 8
⢠Minimal syntactical changes in Java 8
⢠Streams are as functional as Java gets
Hot Streaming Java
8. 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
22. 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
23. 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();
26. 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
27. 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
30. 30Hot Streaming Java
Stream grep âc
Imperative 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
31. 31Hot Streaming Java
Stream grep âc
Using 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
32. 32Hot Streaming Java
Stream grep âc
Using 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
33. 33Hot Streaming Java
Stream grep âc
Using 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
34. 34Hot Streaming Java
Stream grep âc
Using 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
36. 36Hot Streaming Java
Stream grep
Foreach-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
37. 37Hot Streaming Java
Stream grep
Reduction-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
38. 38Hot Streaming Java
Stream grep
Mutable 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
40. 40Hot Streaming Java
Stream grep
Mutable 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âŚ
41. 41Hot Streaming Java
Stream grep
Mutable 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
42. 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
43. 43Hot Streaming Java
Stream grep
Parallel 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());
}
47. 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
48. 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
49. 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
52. 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 point
int mid = array.get((lo + hi) / 2);
// Step 2: find & swap values less & greater than pivot
while (lo < hi) {
while (lo < hi && array.get(lo) < mid) {
lo++;
}
while (lo < hi && array.get(hi) > mid) {
hi--;
}
if (lo < hi) {
// Swap values
int 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 recursively
imperativeQuickSort(array, low, lo);
imperativeQuickSort(array, lo == low ? lo + 1 : lo, n);
Imperative
quick sort
53. 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
59. Parallel streams
Parallel streams are
best suited for
algorithms that⌠Can split data easily
& quickly
Have no
order
constraint
Can reduce
concurrently
Have large
N and/or Q
Hot Streaming Java
61. 61Hot Streaming Java
Parallel streaming
Sometimes, parallel streams are slower
than their serial counterpartsâŚ
And also their imperative counterparts
63. 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
Declarative: we say what we want not how
Opposite of imperative programing
Java 8 only introduced
From a Collection via the stream() and parallelStream() methods;
From an array via Arrays.stream(Object[]);
From static factory methods on the stream classes, such as Stream.of(Object[]), IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator);
The lines of a file can be obtained from BufferedReader.lines();
Streams of file paths can be obtained from methods in Files;
Streams of random numbers can be obtained from Random.ints();
Numerous other stream-bearing methods in the JDK, including BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence)
forEach lambda has side-effects, interferes with stream parallelization, incorrect processing and not thread-safe