9. Journey’s End, JavaOne, September 2016
• Why collectors?
• Using the predefined collectors
• Worked problems
• Writing your own
Journey’s End – Agenda
10. Journey’s End, JavaOne, September 2016
Example Domain
city: City
name: String
age: int
Person
Person amy = new Person(Athens, "Amy", 21);
...
List<Person> people = Arrays.asList(jon, amy, bill);
11. Journey’s End, JavaOne, September 2016
Collectors: Journey’s End for a Stream
The Life of a Stream Element
• Born (at a spliterator)
• Transformed (by intermediate operations)
• Collected (by a terminal operation)
12. Journey’s End, JavaOne, September 2016
Collectors: Journey’s End for a Stream
The Life of a Stream Element
• Born (at a spliterator)
• Transformed (by intermediate operations)
• Collected (by a terminal operation)
13. Journey’s End, JavaOne, September 2016
Collectors: Journey’s End for a Stream
The Life of a Stream Element
• Born (at a spliterator)
• Transformed (by intermediate operations)
• Collected (by a terminal operation)
14. Journey’s End, JavaOne, September 2016
Collectors: Journey’s End for a Stream
?
The Life of a Stream Element
• Born (at a spliterator)
• Transformed (by intermediate operations)
• Collected (by a terminal operation)
17. Journey’s End, JavaOne, September 2016
Side-effecting Operations
Side-effecting operations
• forEach, forEachOrdered
people.stream()
.forEach(System.out::println);
• So could we calculate total ages like this?
int sum = 0;
people.stream()
.mapToInt(Person::getAge)
.forEach(a -> { sum += a });
people.stream()
.forEach(System.out::println);
int sum = 0;
people.stream()
.mapToInt(Person::getAge)
.forEach(a -> { sum += a; });
18. Journey’s End, JavaOne, September 2016
Side-effecting Operations
Side-effecting operations
• forEach, forEachOrdered
people.stream()
.forEach(System.out::println);
• So could we calculate total ages like this?
int sum = 0;
people.stream()
.mapToInt(Person::getAge)
.forEach(a -> { sum += a });
people.stream()
.forEach(System.out::println);
int sum = 0;
people.stream()
.mapToInt(Person::getAge)
.forEach(a -> { sum += a; });
Don’t do this!
19. Journey’s End, JavaOne, September 2016
• Using an accumulator:
Reduction – Why?
int[] vals = new int[100];
Arrays.setAll(vals, i -> i);
int sum = 0;
for (int i = 0 ; i < vals.length ; i++) {
sum += vals[i];
}
20. Journey’s End, JavaOne, September 2016
• Using an accumulator:
Reduction – Why?
int[] vals = new int[100];
Arrays.setAll(vals, i -> i);
int sum = 0;
for (int i = 0 ; i < vals.length ; i++) {
sum += vals[i];
}
0 1 2 3
+
+
+
23. Journey’s End, JavaOne, September 2016
• Avoiding an accumulator:
Reduction – Why?
int[] vals = new int[100];
Arrays.setAll(vals, i -> i);
OptionalInt sum = Arrays.stream(vals)
.reduce((a,b) -> a + b);
+ +
+
1 2 30
24. Journey’s End, JavaOne, September 2016
• Avoiding an accumulator:
Reduction – Why?
int[] vals = new int[100];
Arrays.setAll(vals, i -> i);
OptionalInt sum = Arrays.stream(vals)
.reduce((a,b) -> a + b);
+ +
+
1 2 30
+
+
+
25. Journey’s End, JavaOne, September 2016
• Avoiding an accumulator:
Reduction – Why?
int[] vals = new int[100];
Arrays.setAll(vals, i -> i);
OptionalInt sum = Arrays.stream(vals)
.reduce((a,b) -> a + b);
BinaryOperator must be associative!
+ +
+
1 2 30
+
+
+
26. Journey’s End, JavaOne, September 2016
• Avoiding an accumulator:
Reduction – Why?
int[] vals = new int[100];
Arrays.setAll(vals, i -> i);
OptionalInt sum = Arrays.stream(vals)
.reduce((a,b) -> a + b);
BinaryOperator must be associative!
a + (b + c) = (a + b) + c
+ +
+
1 2 30
+
+
+
30. Journey’s End, JavaOne, September 2016
Reduction on ImmutableValues
Reduction works on immutable values too
BigDecimal[] vals = new BigDecimal[100];
Arrays.setAll(vals, i -> new BigDecimal(i));
Optional<BigDecimal> sum = Arrays.stream(vals)
.reduce(BigDecimal::add);
32. Journey’s End, JavaOne, September 2016
Reduction to Collections?
This also works with collections – sort of ...
• for each element, client code creates a new empty
collection and adds the single element to it
• combines collections using addAll
33. Journey’s End, JavaOne, September 2016
Reduction to Collections?
This also works with collections – sort of ...
• for each element, client code creates a new empty
collection and adds the single element to it
• combines collections using addAll
We can do better!
34. Journey’s End, JavaOne, September 2016
Reduction over an Identity
BigDecimal[] vals = new BigDecimal[100];
Arrays.setAll(vals, i -> new BigDecimal(i));
BigDecimal sum = Arrays.stream(vals)
.reduce(BigDecimal.ZERO,BigDecimal::add);
35. Journey’s End, JavaOne, September 2016
Reduction over an Identity
BigDecimal[] vals = new BigDecimal[100];
Arrays.setAll(vals, i -> new BigDecimal(i));
BigDecimal sum = Arrays.stream(vals)
.reduce(BigDecimal.ZERO,BigDecimal::add);
Works for immutable objects:
36. Journey’s End, JavaOne, September 2016
Reduction over an Identity
+
+
+
0 1 2 3
+
+
+
0
4 5 6 7
0
++
+
38. Journey’s End, JavaOne, September 2016
Reduction to Collections?
Reduction over an identity doesn’t work with
collections at all
• reduction reuses the identity element
39. Journey’s End, JavaOne, September 2016
Reduction to Collections?
Reduction over an identity doesn’t work with
collections at all
• reduction reuses the identity element
We’ve got to do better!
40. Journey’s End, JavaOne, September 2016
The Answer – Collectors
a
a
a
e0 e1 e2 e3
a
a
a
e4 e5 e6 e7
aa
c
41. Journey’s End, JavaOne, September 2016
The Answer – Collectors
a
a
a
e0 e1 e2 e3
a
a
a
() -> []
e4 e5 e6 e7
aa
c
() -> []
42. Journey’s End, JavaOne, September 2016
The Answer – Collectors
a
a
a
e0 e1 e2 e3
a
a
a
() -> []
e4 e5 e6 e7
aa
c
a: accumulator
c: combiner
() -> []
44. Journey’s End, JavaOne, September 2016
Collectors
So to define a collector, you need to provide
• Supplier
• Accumulator
• Combiner
45. Journey’s End, JavaOne, September 2016
Collectors
So to define a collector, you need to provide
• Supplier
• Accumulator
• Combiner
That sounds really hard!
46. Journey’s End, JavaOne, September 2016
Collectors
So to define a collector, you need to provide
• Supplier
• Accumulator
• Combiner
That sounds really hard!
Good then that we don’t have to do it
… very often
47. Journey’s End, JavaOne, September 2016
• Why collectors?
• Using the predefined collectors
• Worked problems
• Writing your own
Journey’s End – Agenda
48. Journey’s End, JavaOne, September 2016
Collectors API
Factory methods in the Collectors class.They
produce standalone collectors, accumulating to:
• framework-supplied containers
• custom collections
• classification maps
49. Journey’s End, JavaOne, September 2016
Predefined Standalone Collectors – from factory
methods in Collectors class
• toList(), toSet(), toMap(), joining()
• toMap(), toCollection()
• groupingBy(), partitioningBy(),
groupingByConcurrent()
Using the Predefined Collectors
50. Journey’s End, JavaOne, September 2016
Predefined Standalone Collectors – from factory
methods in Collectors class
• toList(), toSet(), toMap(), joining()
• toMap(), toCollection()
• groupingBy(), partitioningBy(),
groupingByConcurrent()
Using the Predefined Collectors
framework provides
the Supplier
51. Journey’s End, JavaOne, September 2016
Predefined Standalone Collectors – from factory
methods in Collectors class
• toList(), toSet(), toMap(), joining()
• toMap(), toCollection()
• groupingBy(), partitioningBy(),
groupingByConcurrent()
Using the Predefined Collectors
user provides
the Supplier
framework provides
the Supplier
52. Journey’s End, JavaOne, September 2016
Predefined Standalone Collectors – from factory
methods in Collectors class
• toList(), toSet(), toMap(), joining()
• toMap(), toCollection()
• groupingBy(), partitioningBy(),
groupingByConcurrent()
Using the Predefined Collectors
user provides
the Supplier
framework provides
the Supplier
produce a
classification map
67. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
bill
68. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
bill
Person::getCity
69. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
London
bill
70. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
London
bill Person::getName
71. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
London “Bill”
bill Person::getName
72. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
London “Bill”
73. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
amy
London “Bill”
74. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
Athens
amy
London “Bill”
75. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
“Amy”Athens
London “Bill”
76. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
“Amy”Athens
jon
London “Bill”
77. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
Stream<Person>
toMap() Map<City,String>
“Amy”Athens
London “Bill”
Tulsa “Jon”
78. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
toMap() Map<City,String>
“Amy”Athens
London “Bill”
Tulsa “Jon”
79. Journey’s End, JavaOne, September 2016
toMap(Function<T,K> keyMapper,
Function<T,U> valueMapper)
toMap() Map<City,String>
“Amy”Athens
London “Bill”
Tulsa “Jon”
91. Journey’s End, JavaOne, September 2016
Collectors API
Factory methods in the Collectors class.They
produce standalone collectors, accumulating to:
• framework-supplied containers
• custom collections
• classification maps
100. Journey’s End, JavaOne, September 2016
Collectors API
Factory methods in the Collectors class.They
produce standalone collectors, accumulating to:
• framework-supplied containers
• custom collections
• classification maps
101. Journey’s End, JavaOne, September 2016
Collecting to Classification Maps
groupingBy
• simple
• with downstream
• with downstream and map factory
partitioningBy
102. Journey’s End, JavaOne, September 2016
groupingBy(Function<T,K> classifier)
Uses the classifier function to make a classification mapping
Like toMap(), except that the values placed in the map are lists of
the elements, one List corresponding to each classification key:
For example, use Person.getCity() to make a Map<City,List<Person>>
Map<City,List<Person>> peopleByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
103. Journey’s End, JavaOne, September 2016
Stream<Person>
groupingBy() Map<City,List<Person>>
bill
jon
amy Athens
London
Collector<Person,?,Map<City,List<Person>
groupingBy(Function<Person,City>)
Classifier
Person→City
104. Journey’s End, JavaOne, September 2016
Stream<Person>
groupingBy() Map<City,List<Person>>
bill
jon
amy Athens
London
groupingBy(Function<Person,City>)
105. Journey’s End, JavaOne, September 2016
Stream<Person>
groupingBy() Map<City,List<Person>>
bill
jon
amy Athens
bill London
groupingBy(Function<Person,City>)
106. Journey’s End, JavaOne, September 2016
Stream<Person>
groupingBy() Map<City,List<Person>>
bill
jon
amy Athens
billLondon
groupingBy(Function<Person,City>)
[ ]
107. Journey’s End, JavaOne, September 2016
Stream<Person>
groupingBy() Map<City,List<Person>>
bill
jon
amy Athens [ ]
bill
amy
London
groupingBy(Function<Person,City>)
[ ]
108. Journey’s End, JavaOne, September 2016
Stream<Person>
groupingBy() Map<City,List<Person>>
bill
jon
amy Athens [ ]
[ , ]bill
amy
jonLondon
groupingBy(Function<Person,City>)
109. Journey’s End, JavaOne, September 2016
groupingBy() Map<City,List<Person>>
bill
jon
amy Athens [ ]
[ , ]bill
amy
jonLondon
groupingBy(Function<Person,City>)
110. Journey’s End, JavaOne, September 2016
groupingBy() Map<City,List<Person>>
bill
jon
amy
Athens [ ]
[ , ]bill
amy
jonLondon
groupingBy(Function<Person,City>)
111. Journey’s End, JavaOne, September 2016
groupingBy(Function<T,K> classifier)
Uses the classifier function to make a classification mapping
Like toMap(), except that the values placed in the map are lists of
the elements, one List corresponding to each classification key:
For example, use Person.getCity() to make a Map<City,List<Person>>
Map<City,List<Person>> peopleByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
112. Journey’s End, JavaOne, September 2016
groupingBy(classifier, downstream)
Uses the classifier function to make a classification mapping into a
container defined by a downstream Collector
Like toMap(), except that the values placed in the map are containers
of the elements, one container corresponding to each classification key:
For example, use Person.getCity() to make a Map<City,Set<Person>>
Map<City,List<Person>> peopleByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity,toSet()));
113. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens
Downstream
Collector
—
toSet()
Stream<Person
>
Classifier
Person→City
114. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens
115. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens
bill
116. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens
bill
117. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens
bill
118. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athensamy
bill
119. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens amy
bill
120. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens amy
bill
121. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens amy
jon bill
122. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
Stream<Person>
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens amy
jon
bill
123. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens amy
jon
bill
124. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens amy
jon
bill
125. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens { }amy
{ , }jonbill
126. Journey’s End, JavaOne, September 2016
groupingBy(Function classifier,Collector downstream))
groupingBy()
Map<City,Set<Person>
bill
jon
amy
London
Athens { }amy
{ , }jonbill
127. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
Applies a mapping function to each input element before passing it
to the downstream collector.
128. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
Applies a mapping function to each input element before passing it
to the downstream collector.
Set<City> inhabited = people.stream()
.collect(mapping(Person::getCity,toSet()));
Animation example
129. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
LondonStream<Person>
mapping()
bill
jon
amy Athens
Mapper
Person→City
Downstream
Collector
—
toSet()
Stream<City>
Set<City>
130. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
LondonStream<Person>
mapping()
bill
jon
amy Athens
Set<City>
131. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
LondonStream<Person>
mapping()
bill
jon
amy Athens
London
Set<City>
132. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
LondonStream<Person>
mapping()
bill
jon
amy Athens
Athens
London
Set<City>
133. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
LondonStream<Person>
mapping()
bill
jon
amy Athens
Athens
LondonLondon
Set<City>
134. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
London
mapping()
bill
jon
amy Athens
Athens
LondonLondon
Set<City>
135. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
London
mapping()
bill
jon
amy
{ ,
}
Athens
Athens
London
Set<City>
136. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
London
mapping()
bill
jon
amy
{ ,
}
Athens
Athens
London
Set<City>
137. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
Applies a mapping function to each input element before passing it
to the downstream collector.
Set<City> inhabited = people.stream()
.collect(mapping(Person::getCity,toSet()));
Animation example
138. Journey’s End, JavaOne, September 2016
mapping(Function mapper,Collector downstream))
Applies a mapping function to each input element before passing it
to the downstream collector.
Set<City> inhabited = people.stream()
.collect(mapping(Person::getCity,toSet()));
Map<City,String> namesByCity = people.stream()
.collect(groupingBy(Person::getCity,
mapping(Person::getName,joining()));
Animation example
Sensible example
139. Collectors’ Fair, JavaOne, October 2016
groupingBy()
bill
jon
amy Athens
billamyjon
London
Collector<Person,?,String>
Classifier
Person→City
JDK Collectors with Java 8 Streams
mapping()
Mapper
Person→String
Stream<String>
joining()
Collector<CharSequence,?,String>
143. Journey’s End, JavaOne, September 2016
Thread safety is guaranteed by the framework
• Even for non-threadsafe containers!
Concurrent Collection
144. Journey’s End, JavaOne, September 2016
Thread safety is guaranteed by the framework
• Even for non-threadsafe containers!
But at a price... So what if your container is already threadsafe?
• A concurrentMap implementation, for example?
Concurrent Collection
145. Journey’s End, JavaOne, September 2016
Thread safety is guaranteed by the framework
• Even for non-threadsafe containers!
But at a price... So what if your container is already threadsafe?
• A concurrentMap implementation, for example?
Concurrent Collection
146. Journey’s End, JavaOne, September 2016
Thread safety is guaranteed by the framework
• Even for non-threadsafe containers!
But at a price... So what if your container is already threadsafe?
• A concurrentMap implementation, for example?
Every overload of toMap() and groupingBy() has a dual
• toConcurrentMap(...)
• groupingByConcurrent(...)
Concurrent Collection
147. Journey’s End, JavaOne, September 2016
• Why collectors?
• Using the predefined collectors
• Worked problems
• Writing your own
Journey’s End – Agenda
152. Most Popular Age
Optional<Integer> modalAge = populationByAge
.entrySet()
.stream()
.sorted(Entry.<Integer,Long>comparingByValue().reversed())
153. Most Popular Age
Optional<Integer> modalAge = populationByAge
.entrySet()
.stream()
.sorted(Entry.<Integer,Long>comparingByValue().reversed())
.map(Entry::getKey)
154. Most Popular Age
Optional<Integer> modalAge = populationByAge
.entrySet()
.stream()
.sorted(Entry.<Integer,Long>comparingByValue().reversed())
.map(Entry::getKey)
.findFirst();
159. Most Popular Age
Optional<Integer> modalAge = populationByAge
.entrySet()
.stream()
.collect(maxBy(Comparator.comparing(Map.Entry::getValue)))
160. Most Popular Age
Optional<Integer> modalAge = populationByAge
.entrySet()
.stream()
.collect(maxBy(Comparator.comparing(Map.Entry::getValue)))
.map(Map.Entry::getKey);
161. Most Popular Age
Optional<Integer> modalAge = populationByAge
.entrySet()
.stream()
.collect(maxBy(Comparator.comparing(Map.Entry::getValue)))
.map(Map.Entry::getKey);
162. Map<Long, Set<Integer>> collect = people.stream()
.collect(groupingBy(People::getAge, counting()))
.entrySet()
.stream()
.collect(groupingBy(Entry::getValue,
mapping(Entry::getKey, toSet())));
Most Popular Ages
163. Journey’s End, JavaOne, September 2016
• Why collectors?
• Using the predefined collectors
• Worked problems
• Writing your own
Journey’s End – Agenda
166. Journey’s End, JavaOne, September 2016
Writing a Collector
Why would you want to?
• accumulate to a container that doesn’t implement Collection
167. Journey’s End, JavaOne, September 2016
Writing a Collector
Why would you want to?
• accumulate to a container that doesn’t implement Collection
• share state between values being collected
186. Journey’s End, JavaOne, September 2016
Performance of Collectors
Depends on the performance of accumulator and combiner
functions
• toList(), toSet(), toCollection – performance will probably be
dominated by accumulator, but remember that the framework will
need to manage multithread access to non-threadsafe containers
for the combine operation
toMap(), toConcurrentMap()
• map merging is slow. Resizing maps, especially concurrent maps, is
particularly expensive.Whenever possible, presize all data
structures, maps in particular.
187. Journey’s End, JavaOne, September 2016
Conclusion
Collectors
• generalisation of reduction
• allow a functional style while continuing to work with a
language based on mutation
• designed for composition
• flexible and powerful programming tool
• take a bit of getting used to! – but they’re worth it!