-
Notifications
You must be signed in to change notification settings - Fork 90
/
ch17.html
1025 lines (696 loc) · 69.4 KB
/
ch17.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Study guide for the Oracle Certified Professional, Java SE 8 Programmer Exam ">
<title>Java 8 Programmer II Study Guide: Exam 1Z0-809</title>
<link href="css/code.css" rel="stylesheet" type="text/css" />
<link href="css/style.css" rel="stylesheet" type="text/css" />
<link href="https://netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script src="js/common-sections.js"></script>
</head>
<body>
<div class="nav"></div>
<div class="header">
<div class="title-container">
<div class="chapter-title">
<h1><i class="chapter">Chapter SEVENTEEN</i><br />
Peeking, Mapping, Reducing and Collecting</h1>
<p><br /></p>
<h3 style="text-align: center;"><i>Exam Objectives</i></h3>
<p style="text-align: center;"><i>Develop code to extract data from an object using peek() and map() methods including primitive versions of the map() method.<br /></i><i>Save results to a collection using the collect method and group/partition data using the Collectors class.<br /></i><i>Use of merge() and flatMap() methods of the Stream API.</i></p>
</div>
</div>
</div>
<div class="container">
<div class="column">
<h2>peek()</h2>
<p><code>peek()</code> is a simple method:</p>
<p><code class="java hljs"><span class="hljs-function">Stream<T> <span class="hljs-title">peek</span><span class="hljs-params">(Consumer<? <span class="hljs-keyword">super</span> T> action)</span></span></code></p>
<p>It just executes the provided <code>Consumer</code> and returns a new stream with the same elements of the original one.</p>
<p>Most of the time, this method is used with <code>System.out.println()</code> for debugging purposes (to see what's on the stream):</p>
<p><code class="java hljs">System.out.format(<span class="hljs-string">"\n%d"</span>,<br />
IntStream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.limit(<span class="hljs-number">3</span>)<br />
.peek( i -><br />
System.out.format(<span class="hljs-string">"%d "</span>, i) )<br />
.sum() );</code></p>
<p>The output:</p>
<p><code class="java hljs">1 2 3<br />
6</code></p>
<p>Notice <code>peek()</code> is an intermediate operation. In the example, we can't use something like <code>forEach()</code> to print the values returned by <code>limit()</code> because <code>forEach()</code> is a terminal operation (and we couldn't call <code>sum()</code> anymore).</p>
<p>It's important to emphasize that <code>peek()</code> is intended to see the elements of a stream in a particular point of the pipeline, it's considered bad practice to change the stream in any way. If you want to do that, use the following method.</p>
<h2>map()</h2>
<p><code>map()</code> is used to transform the value or the type of the elements of a stream:</p>
<p><code class="java hljs"><R> <span class="hljs-function">Stream<R> <span class="hljs-title">map</span><span class="hljs-params">(Function<? <span class="hljs-keyword">super</span> T,? extends R> mapper)</span><br />
<br />
IntStream <span class="hljs-title">mapToInt</span><span class="hljs-params">(ToIntFunction<? <span class="hljs-keyword">super</span> T> mapper)</span><br />
<br />
LongStream <span class="hljs-title">mapToLong</span><span class="hljs-params">(ToLongFunction<? <span class="hljs-keyword">super</span> T> mapper)</span><br />
<br />
DoubleStream <span class="hljs-title">mapToDouble</span><span class="hljs-params">(ToDoubleFunction<? <span class="hljs-keyword">super</span> T> mapper)</span></span></code></p>
<p>As you can see, <code>map()</code> takes a <code>Function</code> to convert the elements of a stream of type <code>T</code> to type <code>R</code>, returning a stream of that type <code>R</code>:</p>
<p><code class="java hljs">Stream.of(<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>, <span class="hljs-string">'d'</span>, <span class="hljs-string">'e'</span>)<br />
.map(c -> (<span class="hljs-keyword">int</span>)c)<br />
.forEach(i -> System.out.format(<span class="hljs-string">"%d "</span>, i));</code></p>
<p>The output:</p>
<p><code class="java hljs">97 98 99 100 101</code></p>
<p>There are versions for transforming to primitive types, for example:</p>
<p><code class="java hljs">IntStream.of(<span class="hljs-number">100</span>, <span class="hljs-number">110</span>, <span class="hljs-number">120</span>, <span class="hljs-number">130</span> ,<span class="hljs-number">140</span>)<br />
.mapToDouble(i -> i/<span class="hljs-number">3.0</span>)<br />
.forEach(i -> System.out.format(<span class="hljs-string">"%.2f "</span>, i));</code></p>
<p>Will output:</p>
<p><code class="java hljs">33.33 36.67 40.00 43.33 46.67</code></p>
<h2>flatMap()</h2>
<p><code>flatMap()</code> is used to "flatten" (or combine) the elements of a stream into one (new) stream:</p>
<p><code class="java hljs"><R> <span class="hljs-function">Stream<R> <span class="hljs-title">flatMap</span><span class="hljs-params">(Function<? <span class="hljs-keyword">super</span> T,<br />
? extends Stream<? extends R>> mapper)</span><br />
<br />
DoubleStream <span class="hljs-title">flatMapToDouble</span><span class="hljs-params">(Function<? <span class="hljs-keyword">super</span> T,<br />
? extends DoubleStream> mapper)</span><br />
<br />
IntStream <span class="hljs-title">flatMapToInt</span><span class="hljs-params">(Function<? <span class="hljs-keyword">super</span> T,<br />
? extends IntStream> mapper)</span><br />
<br />
LongStream <span class="hljs-title">flatMapToLong</span><span class="hljs-params">(Function<? <span class="hljs-keyword">super</span> T,<br />
? extends LongStream> mapper)</span></span></code></p>
<p>If the <code>Function</code> used by <code>flatMap()</code> returns <code>null</code>, an empty stream is returned instead.</p>
<p>Let's see how this work. If we have a stream of lists of characters:</p>
<p><code class="java hljs">List<Character> aToD = Arrays.asList(<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>, <span class="hljs-string">'d'</span>);<br />
List<Character> eToG = Arrays.asList(<span class="hljs-string">'e'</span>, <span class="hljs-string">'f'</span>, <span class="hljs-string">'g'</span>);<br />
Stream<List<Character>> stream = Stream.of(aToD, eToG);</code></p>
<p>And we want to convert all the characters to their <code>int</code> representation, what we need to do is to get the elements of the lists into one stream and then convert each character to an <code>int</code>. Fortunately, the "combining" part is exactly what <code>flatMap()</code> does:</p>
<p><code class="java hljs">stream<br />
.flatMap(l -> l.stream())<br />
.map(c -> (<span class="hljs-keyword">int</span>)c)<br />
.forEach(i -> System.out.format(<span class="hljs-string">"%d "</span>, i));</code></p>
<p>So this code can output:</p>
<p><code class="java hljs">97 98 99 100 101 102 103</code></p>
<p>This way, with <code>flatMap()</code> you can convert a <code>Stream<List<T>></code> to <code>Stream<T></code>.</p>
<p>Using <code>peek()</code> after <code>flatMap()</code> may clarify how the elements are processed:</p>
<p><code class="java hljs">stream<br/>
.flatMap(l -> l.stream())<br/>
.peek(System.out::print)<br/>
.map(c -> (<span class="hljs-keyword">int</span>)c)<br/>
.forEach(i -> System.out.format(<span class="hljs-string">"%d "</span>, i));</code></p>
<p>As you can see from the output, the stream returned from <code>flatMap()</code> is passed through the pipeline, as if we were working with a stream of single elements and not with a stream of lists of elements:</p>
<p><code class="java hljs">a97 b98 c99 d100 e101 f102 g103</code></p>
<p>In both cases, <code>map()</code> and <code>flatMap()</code> return a stream. <code>map()</code> returns <code>Stream<Integer></code> and <code>flatMap()</code> returns <code>Stream<Character></code>.</p>
<p>In both cases, <code>map()</code> and <code>flatMap()</code> take a <code>Function</code> as its argument, but each <code>Function</code> has different parameters. <code>Function<Character, Integer></code> and <code>Function<List<Character>, Stream<? extends Character>></code>, respectively.</p>
<h2>Reduction</h2>
<p>A reduction is an operation that takes many elements and combines them (o <i>reduce</i> them) into a single value or object, and it is done by applying an operation multiple times.</p>
<p>Some examples of reductions are summing <code>N</code> elements, finding the maximum element of <code>N</code> numbers, or counting elements.</p>
<p>Like in the following example, where using a <code>for</code> loop, we reduce an array of numbers to their sum:</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span>[] numbers = {<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>};<br />
<span class="hljs-keyword">int</span> sum = <span class="hljs-number">0</span>;<br />
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> n : numbers) {<br />
sum += n;<br />
}</code></p>
<p>Of course, making reductions with streams instead of loops has more benefits, like easier parallelization and improved readability.</p>
<p>The Stream interface has two methods for reduction:</p>
<ul>
<li><code>reduce()</code></li>
<li><code>collect()</code></li>
</ul>
<p>We can implement reductions with both methods, but <code>collect()</code> help us to implement a type of reduction called <i>mutable reduction</i>, where a container (like a <code>Collection</code>) is used to accumulate the result of the operation.</p>
<h2>reduce()</h2>
<p>This method has three versions:</p>
<p><code class="java hljs"><span class="hljs-function">Optional<T> <span class="hljs-title">reduce</span><span class="hljs-params">(BinaryOperator<T> accumulator)<br /></span><br />
T <span class="hljs-title">reduce</span><span class="hljs-params">(T identity,<br />
BinaryOperator<T> accumulator)</span><br />
<br />
<U> U <span class="hljs-title">reduce</span><span class="hljs-params">(U identity,<br />
BiFunction<U,? <span class="hljs-keyword">super</span> T,U> accumulator,<br />
BinaryOperator<U> combiner)</span></span></code></p>
<p>Remember that a <code>BinaryOperator<T></code> is equivalent to a <code>BiFunction<T, T, T></code>, where the two arguments and the return type are all of the same types.</p>
<p>Let's start with the version that takes one argument. This is equivalent to:</p>
<p><code class="java hljs"><span class="hljs-keyword">boolean</span> elementsFound = <span class="hljs-keyword">false</span>;<br />
T result = <span class="hljs-keyword">null</span>;<br />
<span class="hljs-keyword">for</span> (T element : stream) {<br />
<span class="hljs-keyword"> if</span> (!elementsFound) {<br />
elementsFound = <span class="hljs-keyword">true</span>;<br />
result = element;<br />
} <span class="hljs-keyword">else</span> {<br />
result = accumulator.apply(result, element);<br />
}<br />
<span class="hljs-keyword">return</span> elementsFound ? Optional.of(result)<br />
: Optional.empty();</code></p>
<p>This code just applies a function for each element, accumulating the result and returning an <code>Optional</code> wrapping that result, or an empty <code>Optional</code> if there were no elements.</p>
<p>Let's see a concrete example. We just see how a sum is a reduce operation:</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span>[] numbers = {<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>};<br />
<span class="hljs-keyword">int</span> sum = <span class="hljs-number">0</span>;<br />
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> n : numbers) {<br />
sum += n;<br />
}</code></p>
<p>Here, the accumulator operation is:</p>
<p><code class="java hljs">sum += n;</code></p>
<p>Or:</p>
<p><code class="java hljs">sum = sum + n;</code></p>
<p>Which translate to:</p>
<p><code class="java hljs">OptionalInt total = IntStream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.reduce( (sum, n) -> sum + n );</code></p>
<p>(Notice how the primitive version of stream uses the primitive version of <code>Optional</code>).</p>
<p>This is what happens step by step:</p>
<p>1. An internal variable that accumulates the result is set to the first element of a stream (<code>1</code>).<br /> 2. This accumulator and the second element of the stream (<code>2</code>) are passed as arguments to the <code>BinaryOperator</code> represented by the lambda expression <code>(sum, n) -> sum + x</code>.<br /> 3. The result (<code>3</code>) is assigned to the accumulator.<br /> 4. The accumulator (<code>3</code>) and the third element of the stream (<code>3</code>) are passed as arguments to the <code>BinaryOperator</code>.<br /> 5. The result (<code>6</code>) is assigned to the accumulator.<br /> 6. Steps 4 and 5 are repeated for the next elements of the stream until there are no more elements.</p>
<p>However, what if you need to have an initial value? For cases like that, we have the version that takes two arguments:</p>
<p><code class="java hljs"><span class="hljs-function">T <span class="hljs-title">reduce</span><span class="hljs-params">(T identity, BinaryOperator<T> accumulator)</span></span></code></p>
<p>The first argument is the initial value, and it is called identity because strictly speaking, this value must be an identity for the accumulator function, in other words, for each value <code>v</code>, <code>accumulator.apply(identity, v)</code> must be equal to <code>v</code>.</p>
<p>This version of <code>reduce()</code> is equivalent to:</p>
<p><code class="java hljs">T result = identity;<br />
<span class="hljs-keyword">for</span> (T element : stream) {<br />
result = accumulator.apply(result, element);<br />
} <br />
<span class="hljs-keyword">return</span> result;</code></p>
<p>Notice that this version does not return an <code>Optional</code> object because if the stream empty, the identity value is returned.</p>
<p>For example, the sum example can be rewritten as:</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span> total = IntStream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.reduce( <span class="hljs-number">0</span>,<br />
(sum, n) -> sum + n ); <span class="hljs-comment">// 21</span></code></p>
<p>Or using a different initial value:</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span> total = IntStream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.reduce( <span class="hljs-number">4</span>,<br />
(sum, n) -> sum + n ); <span class="hljs-comment">// 25</span></code></p>
<p>However, notice that in the last example, the first value cannot be considered an identity (as in the first example) since, for instance, <code>4 + 1</code> is not equal to <code>4</code>.</p>
<p>This can bring some problems when working with parallel streams, which we'll review in the next chapter.</p>
<p>Now, notice that with these versions, you take elements of type <code>T</code> and return a reduced value of type <code>T</code> also.</p>
<p>However, if you want to return a reduced value of a different type, you have to use the three arguments version of <code>reduce()</code>:</p>
<p><code class="java hljs"><U> <span class="hljs-function">U <span class="hljs-title">reduce</span><span class="hljs-params">(U identity,<br />
BiFunction<U,? <span class="hljs-keyword">super</span> T, U> accumulator,<br />
BinaryOperator<U> combiner)</span></span></code></p>
<p>(Notice the use of types <code>T</code> and <code>U</code>)</p>
<p>This version is equivalent to: </p>
<p><code class="java hljs">U result = identity;<br />
<span class="hljs-keyword">for</span> (T element : stream) {<br />
result = accumulator.apply(result, element) <br />
}<br />
<span class="hljs-keyword">return</span> result;</code></p>
<p>Consider for example that we want to get the sum of the length of all strings of a stream, so we are getting strings (type <code>T</code>), and we want an integer result (type <code>U</code>).</p>
<p>In that case, we use <code>reduce()</code> like this:</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span> length =<br />
Stream.of(<span class="hljs-string">"Parallel"</span>, <span class="hljs-string">"streams"</span>, <span class="hljs-string">"are"</span>, <span class="hljs-string">"great"</span>)<br />
.reduce(<span class="hljs-number">0</span>,<br />
(accumInt, str) -><br />
accumInt + str.length(), <span class="hljs-comment">//accumulator</span><br />
(accumInt1, accumInt2) -><br />
accumInt1 + accumInt2);<span class="hljs-comment">//combiner</span></code></p>
<p>We can make it clearer by adding the argument types:</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span> length =<br />
Stream.of(<span class="hljs-string">"Parallel"</span>, <span class="hljs-string">"streams"</span>, <span class="hljs-string">"are"</span>, <span class="hljs-string">"great"</span>)<br />
.reduce(<span class="hljs-number">0</span>,<br />
(Integer accumInt, String str) -><br />
accumInt + str.length(), <span class="hljs-comment">//accumulator</span><br />
(Integer accumInt1, Integer accumInt2) -><br />
accumInt1 + accumInt2);<span class="hljs-comment">//combiner</span></code></p>
<p>As the accumulator function adds a mapping (transformation) step to the accumulator function, this version of the <code>reduce()</code> can be written as a combination of <code>map()</code> and the other versions of the <code>reduce()</code> method (you may know this as the <i>map-reduce</i> pattern):</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span> length =<br />
Stream.of(<span class="hljs-string">"Parallel"</span>, <span class="hljs-string">"streams"</span>, <span class="hljs-string">"are"</span>, <span class="hljs-string">"great"</span>)<br />
.mapToInt(s -> s.length())<br />
.reduce(<span class="hljs-number">0</span>,<br />
(sum, strLength) -><br />
sum + strLength);</code></p>
<p>Or simply:</p>
<p><code class="java hljs"><span class="hljs-keyword">int</span> length = Stream.of(<span class="hljs-string">"Parallel"</span>, <span class="hljs-string">"streams"</span>, <span class="hljs-string">"are"</span>, <span class="hljs-string">"great"</span>)<br />
.mapToInt(s -> s.length())<br />
.sum();</code></p>
<p>Because, in fact, the calculation operations that we learned about in the last chapter are implemented as reduce operations under the hood:</p>
<ul>
<li><code>average</code></li>
<li><code>count</code></li>
<li><code>max</code></li>
<li><code>min</code></li>
<li><code>sum</code></li>
</ul>
<p>Also, notice that if return a value of the same type, the combiner function is no longer necessary (it turns out that this function is the same as the accumulator function) so, in this case, it's better to use the two argument version.</p>
<p>It's recommended to use the three version <code>reduce()</code> method when:</p>
<ul>
<li>Working with parallel streams (more of this in the next chapter)</li>
<li>Having one function as a mapper and accumulator is more efficient than having separate mapping and reduction functions.</li>
</ul>
<h2>collect()</h2>
<p>This method has two versions:</p>
<p><code class="java hljs"><R,A> <span class="hljs-function">R <span class="hljs-title">collect</span><span class="hljs-params">(Collector<? <span class="hljs-keyword">super</span> T,A,R> collector)</span><br />
<br />
<R> R <span class="hljs-title">collect</span><span class="hljs-params">(Supplier<R> supplier,<br />
BiConsumer<R,? <span class="hljs-keyword">super</span> T> accumulator,<br />
BiConsumer<R,R> combiner)</span></span></code></p>
<p>The first version uses predefined collectors from the <code>Collectors</code> class while the second one allows you to create your own collectors. Primitive streams (like <code>IntStream</code>), only have this last version of <code>collect()</code>.</p>
<p>Remember that <code>collect()</code> performs a mutable reduction on the elements of a stream, which means that it uses a mutable object for accumulating, like a <code>Collection</code> or a <code>StringBuilder</code>. In contrast, <code>reduce()</code> combines two elements to produce a new one and represents an immutable reduction.</p>
<p>However, let's start with the version that takes three arguments, as it's similar to the <code>reduce()</code> version that also takes three arguments.</p>
<p>As you can see from its signature, first, it takes a <code>Supplier</code> that returns the object that will be used as a container (accumulator) and returned at the end.</p>
<p>The second parameter is an accumulator function, which takes the container and the element to be added to it.</p>
<p>The third parameter is the combiner function, which merges the intermediate results into the final one (useful when working with parallel streams).</p>
<p>This version of <code>collect()</code> is equivalent to:</p>
<p><code class="java hljs">R result = supplier.get();<br />
<span class="hljs-keyword">for</span> (T element : stream) {<br />
accumulator.accept(result, element);<br />
} <br />
<span class="hljs-keyword">return</span> result;</code></p>
<p>For example, if we want to "reduce" or "collect" all the elements of a stream into a <code>List</code>, we can do it this way:</p>
<p><code class="java hljs">List<Integer> list =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>)<br />
.collect(<br />
() -> <span class="hljs-keyword">new</span> ArrayList<>(),<span class="hljs-comment">// Creating the container</span><br />
(l, i) -> l.add(i), <span class="hljs-comment">// Adding an element</span><br />
(l1, l2) -> l1.addAll(l2) <span class="hljs-comment">// Combining elements</span><br />
);</code></p>
<p>We can make it clearer by adding the argument types:</p>
<p><code class="java hljs">List<Integer> list =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>)<br />
.collect(<br />
() -> <span class="hljs-keyword">new</span> ArrayList<>(),<br />
(List<Integer> l, Integer i) -> l.add(i),<br />
(List<Integer> l1, List<Integer> l2) -> l1.addAll(l2)<br />
);</code></p>
<p>Or we can also use method references:</p>
<p><code class="java hljs">List<Integer> list =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>)<br />
.collect(<br />
ArrayList::<span class="hljs-keyword">new</span>,<br />
ArrayList::add,<br />
ArrayList::addAll<br />
);</code></p>
<h2>Collectors</h2>
<p>The previous version of <code>collect()</code> is useful to learn how collectors work, but in practice, it's better to use the other version.</p>
<p>Some common collectors of the <code>Collectors</code> class are:</p>
<table border="1" width="100%">
<tr>
<td><b>Method</b></td>
<td><b>Returned value<br />
from collect()</b></td>
<td><b>Description</b></td>
</tr>
<tr>
<td><code>toList</code></td>
<td><code>List</code></td>
<td>Accumulates elements into a <code>List</code>.</td>
</tr>
<tr>
<td><code>toSet</code></td>
<td><code>Set</code></td>
<td>Accumulates elements into a <code>Set</code>.</td>
</tr>
<tr>
<td><code>toCollection</code></td>
<td><code>Collection</code></td>
<td>Accumulates elements into a <code>Collection</code> implementation.</td>
</tr>
<tr>
<td><code>toMap</code></td>
<td><code>Map</code></td>
<td>Accumulates elements into a <code>Map</code>.</td>
</tr>
<tr>
<td><code>joining</code></td>
<td><code>String</code></td>
<td>Concatenates elements into a <code>String</code>.</td>
</tr>
<tr>
<td><code>groupingBy</code></td>
<td><code>Map<K, List<T>></code></td>
<td>Groups elements of type <code>T</code> in lists according to a classification function, into a map with keys of type <code>K</code>.</td>
</tr>
<tr>
<td><code>partitioningBy</code></td>
<td><code>Map<Boolean, List<T>></code></td>
<td>Partitions elements of type <code>T</code> in lists according to a predicate, into a map.</td>
</tr>
</table>
<p>Since calculation methods can be implemented as reductions, the <code>Collectors</code> class also provides them as collectors:</p>
<table border="1" width="100%">
<tr>
<td><b>Method</b></td>
<td><b>Returned value<br />
from collect()</b></td>
<td><b>Description</b></td>
</tr>
<tr>
<td><code>averagingInt<br />
averagingLong<br />
averagingDouble<br /></code></td>
<td><code>Double</code></td>
<td>Returns the average of the input elements.</td>
</tr>
<tr>
<td><code>counting</code></td>
<td><code>Long</code></td>
<td>Counts the elements of input elements.</td>
</tr>
<tr>
<td><code>maxBy</code></td>
<td><code>Optional<T></code></td>
<td>Returns the maximum element according to a given <code>Comparator</code>.</td>
</tr>
<tr>
<td><code>minBy</code></td>
<td><code>Optional<T></code></td>
<td>Returns the minimum element according to a given <code>Comparator</code>.</td>
</tr>
<tr>
<td><code>summingInt<br />
summingLong<br />
summingDouble</code></td>
<td><code>Integer<br />
Long<br />
Double</code></td>
<td>Returns the sum of the input elements.</td>
</tr>
</table>
<p>This way, we can rewrite our previous example:</p>
<p><code class="java hljs">List<Integer> list =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>)<br />
.collect(<br />
ArrayList::<span class="hljs-keyword">new</span>,<br />
ArrayList::add,<br />
ArrayList::addAll<br />
);</code></p>
<p>As:</p>
<p><code class="java hljs">List<Integer> list =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>)<br />
.collect(Collectors.toList()); <span class="hljs-comment">// [1, 2, 3, 4, 5]</span></code></p>
<p>Since all these methods are static, we can use <code>static</code> imports:</p>
<p><code class="java hljs"><span class="hljs-keyword">import</span> <span class="hljs-keyword">static</span> java.util.stream.Collectors.*;<br />
...<br />
List<Integer> list =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>)<br />
.collect(toList()); <span class="hljs-comment">// [1, 2, 3, 4, 5]</span></code></p>
<p>If we want to collect the elements into a <code>Set</code>:</p>
<p><code class="java hljs">Set<Integer> set =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">2</span>, <span class="hljs-number">2</span>)<br />
.collect(toSet()); <span class="hljs-comment">// [1, 2]</span></code></p>
<p>If we want to create another <code>Collection</code> implementation:</p>
<p><code class="java hljs">Deque<Integer> deque =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<br />
.collect(<br />
toCollection(ArrayDeque::<span class="hljs-keyword">new</span>)<br />
); <span class="hljs-comment">// [1, 2, 3,]</span></code></p>
<p>If we are working with streams of <code>String</code>s, we can join all the elements into one <code>String</code> with:</p>
<p><code class="java hljs">String s = Stream.of(<span class="hljs-string">"a"</span>, <span class="hljs-string">"simple"</span>, <span class="hljs-string">"string"</span>)<br />
.collect(joining()); <span class="hljs-comment">// "asimplestring"</span></code></p>
<p>We can also pass a separator:</p>
<p><code class="java hljs">String s = Stream.of(<span class="hljs-string">"a"</span>, <span class="hljs-string">"simple"</span>, <span class="hljs-string">"string"</span>)<br />
.collect(joining(<span class="hljs-string">" "</span>)); <span class="hljs-comment">// " a simple string"</span></code></p>
<p>And a prefix and a suffix:</p>
<p><code class="java hljs">String s = Stream.of(<span class="hljs-string">"a"</span>, <span class="hljs-string">"simple"</span>, <span class="hljs-string">"string"</span>)<br />
.collect(<br />
joining(<span class="hljs-string">" "</span>, <span class="hljs-string">"This is "</span>, <span class="hljs-string">"."</span>)<br />
); <span class="hljs-comment">// "This is a simple string."</span></code></p>
<p>In the case of maps, things get a little more complicated, because depending on our needs, we have three options.</p>
<p>In the first option, <code>toMap()</code> takes two arguments (I'm not showing the return types because they are hard to read and don't provide much value anyway):</p>
<p><code class="java hljs">toMap(Function<? <span class="hljs-keyword">super</span> T,? extends K> keyMapper,<br />
Function<? <span class="hljs-keyword">super</span> T,? extends U> valueMapper)</code></p>
<p>Both <code>Functions</code> take an element of the stream as an argument and return the key or the value of an entry of the <code>Map</code>, for example:</p>
<p><code class="java hljs">Map<Integer, Integer> map =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.collect(<br />
toMap(i -> i, <span class="hljs-comment">// Key</span><br />
i -> i * <span class="hljs-number">2</span> <span class="hljs-comment">// Value</span><br />
)<br />
);</code></p>
<p>Here, we're using the element (like <code>1</code>) as the key, and the element multiplied by two as the value (like <code>2</code>).</p>
<p>We can also write <code>i -> i</code> as <code>Function.identity()</code>:</p>
<p><code class="java hljs">Map<Integer, Integer> map =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.collect(<br />
toMap(Function.identity(), <span class="hljs-comment">// Key</span><br />
i -> i * <span class="hljs-number">2</span> <span class="hljs-comment">// Value</span><br />
)<br />
);</code></p>
<p><code>java.util.function.Function.identity()</code> returns a function that always returns its input argument, in other words, it's equivalent to <code>t -> t</code>.</p>
<p>But what happens if more than one element is mapped to the same key, like in:</p>
<p><code class="java hljs">Map<Integer, Integer> map =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.collect(toMap(i -> i % <span class="hljs-number">2</span>, <span class="hljs-comment">// Key</span><br />
i -> i <span class="hljs-comment">// Value</span><br />
)<br />
);</code></p>
<p>Java won't know what to do, so an exception will be thrown:</p>
<p><code class="java hljs">Exception in thread "main" java.lang.IllegalStateException: Duplicate key 1<br />
at java.util.stream.Collectors.lambda$throwingMerger$113(Collectors.java:133)<br />
at java.util.stream.Collectors$$Lambda$3/303563356.apply(Unknown Source)<br />
at java.util.HashMap.merge(HashMap.java:1245)<br />
at java.util.stream.Collectors.lambda$toMap$171(Collectors.java:1320)</code></p>
<p>For those cases, we use the version that takes three arguments:</p>
<p><code class="java hljs">toMap(Function<? <span class="hljs-keyword">super</span> T,? extends K> keyMapper,<br />
Function<? <span class="hljs-keyword">super</span> T,? extends U> valueMapper,<br />
BinaryOperator<U> mergeFunction)</code></p>
<p>The third argument is a function that defines what to do when there's a duplicate key. For example, we can create a <code>List</code> to append values:</p>
<p><code class="java hljs">Map<Integer, List<Integer>> map =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.collect(toMap(<br />
i -> i % <span class="hljs-number">2</span>,<br />
i -> <span class="hljs-keyword">new</span> ArrayList<Integer>(Arrays.asList(i)),<br />
(list1, list2) -> {<br />
list1.addAll(list2);<br />
<span class="hljs-keyword"> return</span> list1;<br />
}<br />
)<br />
);</code></p>
<p>This will return the following map:</p>
<p><code class="java hljs">{0=[2, 4, 6], 1=[1, 3, 5]}</code></p>
<p>The third version of <code>toMap()</code> takes all these arguments plus one that returns a new, empty <code>Map</code> into which the results will be inserted:</p>
<p><code class="java hljs">toMap(Function<? <span class="hljs-keyword">super</span> T,? extends K> keyMapper,<br />
Function<? <span class="hljs-keyword">super</span> T,? extends U> valueMapper,<br />
BinaryOperator<U> mergeFunction,<br />
Supplier<M> mapSupplier)</code></p>
<p>So we can change the default implementation (<code>HashMap</code>) to <code>ConcurrentHashMap</code> for example:</p>
<p><code class="java hljs">Map<Integer, List<Integer>> map =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>)<br />
.collect(toMap(<br />
i -> i % <span class="hljs-number">2</span>,<br />
i -> <span class="hljs-keyword">new</span> ArrayList<Integer>(Arrays.asList(i)),<br />
(list1, list2) -> {<br />
list1.addAll(list2);<br />
<span class="hljs-keyword"> return</span> list1;<br />
},<br />
ConcurrentHashMap::<span class="hljs-keyword">new</span><br />
)<br />
);</code></p>
<p>About the calculation methods, they are easy to use. Except for <code>counting()</code>, they either take a <code>Function</code> to produce a value to apply the operation, or (in the case of <code>maxBy</code> and <code>minBy</code>) they take a <code>Comparator</code> to produce the result:</p>
<p><code class="java hljs"><span class="hljs-keyword">double</span> avg = Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<br />
.collect(averagingInt(i -> i * <span class="hljs-number">2</span>)); <span class="hljs-comment">// 4.0</span><br />
<span class="hljs-keyword"><br />
long</span> count = Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<br />
.collect(counting()); <span class="hljs-comment">// 3</span><br />
<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<br />
.collect(maxBy(Comparator.naturalOrder()))<br />
.ifPresent(System.out::println); <span class="hljs-comment">// 3</span><br />
<br />
Integer sum = Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>)<br />
.collect(summingInt(i -> i)); <span class="hljs-comment">// 6</span></code></p>
<h2>groupingBy()</h2>
<p>The <code>Collectors</code> class provides two functions to group the elements of a stream into a list, in a kind of an <code>SQL GROUP BY</code> style.</p>
<p>The first method is <code>groupingBy()</code> and it has three versions. This is the first one:</p>
<p><code class="java hljs">groupingBy(Function<? <span class="hljs-keyword">super</span> T,? extends K> classifier)</code></p>
<p>It takes a <code>Function</code> that classifies elements of type <code>T</code>, groups them into a list and returns the result in a <code>Map</code> where the keys (of type <code>K</code>) are the <code>Function</code> returned values.</p>
<p>For example, if we want to group a stream of numbers by the range they belong (tens, twenties, etc.), we can do it with something like this:</p>
<p><code class="java hljs">Map<Integer, List<Integer>> map =<br />
Stream.of(<span class="hljs-number">2</span>, <span class="hljs-number">34</span>, <span class="hljs-number">54</span>, <span class="hljs-number">23</span>, <span class="hljs-number">33</span>, <span class="hljs-number">20</span>, <span class="hljs-number">59</span>, <span class="hljs-number">11</span>, <span class="hljs-number">19</span>, <span class="hljs-number">37</span>)<br />
.collect( groupingBy (i -> i/<span class="hljs-number">10</span> * <span class="hljs-number">10</span> ) );</code></p>
<p>The moment you compare this code with the traditional way to it (with a <code>for</code> loop), it's when you realize the power of streams:</p>
<p><code class="java hljs">List<Integer> stream =<br />
Arrays.asList(<span class="hljs-number">2</span>,<span class="hljs-number">34</span>,<span class="hljs-number">54</span>,<span class="hljs-number">23</span>,<span class="hljs-number">33</span>,<span class="hljs-number">20</span>,<span class="hljs-number">59</span>,<span class="hljs-number">11</span>,<span class="hljs-number">19</span>,<span class="hljs-number">37</span>);<br />
Map<Integer, List<Integer>> map = <span class="hljs-keyword">new</span> HashMap<>();<br />
<span class="hljs-keyword"><br />
for</span>(Integer i : stream) {<br />
<span class="hljs-keyword"> int</span> key = i/<span class="hljs-number">10</span> * <span class="hljs-number">10</span>;<br />
List<Integer> list = map.get(key);<br />
<span class="hljs-keyword"><br />
if</span>(list == <span class="hljs-keyword">null</span>) {<br />
list = <span class="hljs-keyword">new</span> ArrayList<>();<br />
map.put(key, list);<br />
}<br />
list.add(i);<br />
}</code></p>
<p>Either way, those will return the following map:</p>
<p><code class="java hljs">{0=[2], 50=[54,59], 20=[23,20], 10=[11,19], 30=[34,33,37]}</code></p>
<p>The second version takes a <i>downstream collector</i> as an additional argument:</p>
<p><code class="java hljs">groupingBy(Function<? <span class="hljs-keyword">super</span> T,? extends K> classifier,<br />
Collector<? <span class="hljs-keyword">super</span> T,A,D> downstream)</code></p>
<p>A d<i>ownstream collector</i> is a collector that is applied to the results of another collector.</p>
<p>We can use any collector here, for instance, to count the elements in each group of the previous example:</p>
<p><code class="java hljs">Map<Integer, Long> map =<br />
Stream.of(<span class="hljs-number">2</span>, <span class="hljs-number">34</span>, <span class="hljs-number">54</span>, <span class="hljs-number">23</span>, <span class="hljs-number">33</span>, <span class="hljs-number">20</span>, <span class="hljs-number">59</span>, <span class="hljs-number">11</span>, <span class="hljs-number">19</span>, <span class="hljs-number">37</span>)<br />
.collect(<br />
groupingBy(i -> i/<span class="hljs-number">10</span> * <span class="hljs-number">10</span>,<br />
counting()<br />
)<br />
);</code></p>
<p>(Notice how the type of the values of the <code>Map</code> change to reflect the type returned by the downstream collector, <code>counting()</code>)</p>
<p>This will return the following map:</p>
<p><code class="java hljs">{0=1, 50=2, 20=2, 10=2, 30=3}</code></p>
<p>We can even use another <code>groupingBy()</code> to classify the elements in a second level. For instance, instead of counting, we can further classify the elements in even or odd:</p>
<p><code class="java hljs">Map<Integer, Map<String, List<Integer>>> map =<br />
Stream.of(<span class="hljs-number">2</span>,<span class="hljs-number">34</span>,<span class="hljs-number">54</span>,<span class="hljs-number">23</span>,<span class="hljs-number">33</span>,<span class="hljs-number">20</span>,<span class="hljs-number">59</span>,<span class="hljs-number">11</span>,<span class="hljs-number">19</span>,<span class="hljs-number">37</span>)<br />
.collect(groupingBy(i -> i/<span class="hljs-number">10</span> * <span class="hljs-number">10</span>,<br />
groupingBy(i -><br />
i%<span class="hljs-number">2</span> == <span class="hljs-number">0</span> ? <span class="hljs-string">"EVEN"</span> : <span class="hljs-string">"ODD"</span>)<br />
)<br />
);</code></p>
<p>This will return the following map (with a little formatting):</p>
<p><code class="java hljs">{<br />
0 = {EVEN=[2]},<br />
50 = {EVEN=[54], ODD=[59]},<br />
20 = {EVEN=[20], ODD=[23]},<br />
10 = {ODD=[11, 19]},<br />
30 = {EVEN=[34], ODD=[33, 37]}<br />
}</code></p>
<p>The key of the high-level map is an Integer because the first <code>groupingBy()</code> returns an <code>Integer</code>.</p>
<p>The type of the values of the high-level map changed (again) to reflect the type returned by the downstream collector, <code>groupingBy()</code>.</p>
<p>In this case, a <code>String</code> is returned so this will be the type of the keys of the second-level map, and since we are working with a stream of Integers, the values have a type of <code>List<Integer></code>.</p>
<p>Seeing the output of these examples, you may be wondering, is there a way to have the result ordered?</p>
<p>Well, <code>TreeMap</code> is the only implementation of <code>Map</code> that is ordered. Fortunately, the third version of <code>groupingBy()</code> add a <code>Supplier</code> argument that let us choose the type of the resulting <code>Map</code>:</p>
<p><code class="java hljs">groupingBy(Function<? <span class="hljs-keyword">super</span> T,? extends K> classifier,<br />
Supplier<M> mapFactory,<br />
Collector<? <span class="hljs-keyword">super</span> T,A,D> downstream)</code></p>
<p>This way, if we pass an instance of <code>TreeMap</code>:</p>
<p><code class="java hljs">Map<Integer, Map<String, List<Integer>>> map =<br />
Stream.of(<span class="hljs-number">2</span>,<span class="hljs-number">34</span>,<span class="hljs-number">54</span>,<span class="hljs-number">23</span>,<span class="hljs-number">33</span>,<span class="hljs-number">20</span>,<span class="hljs-number">59</span>,<span class="hljs-number">11</span>,<span class="hljs-number">19</span>,<span class="hljs-number">37</span>)<br />
.collect( groupingBy(i -> i/<span class="hljs-number">10</span> * <span class="hljs-number">10</span>,<br />
TreeMap::<span class="hljs-keyword">new</span>,<br />
groupingBy(i -> i%<span class="hljs-number">2</span> == <span class="hljs-number">0</span> ? <span class="hljs-string">"EVEN"</span> : <span class="hljs-string">"ODD"</span>)<br />
)<br />
);</code></p>
<p>This will return the following map:</p>
<p><code class="java hljs">{<br />
0 = {EVEN=[2]},<br />
10 = {ODD=[11, 19]},<br />
20 = {EVEN=[20], ODD=[23]},<br />
30 = {EVEN=[34], ODD=[33, 37]},<br />
50 = {EVEN=[54], ODD=[59]}<br />
}</code></p>
<h2>partitioningBy()</h2>
<p>The second method for grouping is <code>partitioningBy()</code>.</p>
<p>The difference with <code>groupingBy()</code> is that <code>partitioningBy()</code> will return a <code>Map</code> with a <code>Boolean</code> as the key type, which means there are only two groups, one for true and one for <code>false</code>.</p>
<p>There are two versions of this method. The first one is:</p>
<p><code class="java hljs">partitioningBy(Predicate<? <span class="hljs-keyword">super</span> T> predicate)</code></p>
<p>It partitions the elements according to a <code>Predicate</code> and organizes them into a <code>Map<Boolean, List<T>></code>.</p>
<p>For example, if we want to partition a stream of numbers by the ones that are less than <code>50</code> and the ones that don't, we can do it this way:</p>
<p><code class="java hljs">Map<Boolean, List<Integer>> map =<br />
Stream.of(<span class="hljs-number">45</span>, <span class="hljs-number">9</span>, <span class="hljs-number">65</span>, <span class="hljs-number">77</span>, <span class="hljs-number">12</span>, <span class="hljs-number">89</span>, <span class="hljs-number">31</span>)<br />
.collect(partitioningBy(i -> i < <span class="hljs-number">50</span>));</code></p>
<p>This will return the following map:</p>
<p><code class="java hljs">{false=[65, 77, 89], true=[45, 9, 12, 31, 12]}</code></p>
<p>As you can see, because of the <code>Predicate</code>, the map will always have two elements.</p>
<p>And like <code>groupingBy()</code>, this method has a second version that takes a downstream collector.</p>
<p>For example, if we want to remove duplicates, we just have to collect the elements into a <code>Set</code> like this:</p>
<p><code class="java hljs">Map<Boolean, Set<Integer>> map =<br />
Stream.of(<span class="hljs-number">45</span>, <span class="hljs-number">9</span>, <span class="hljs-number">65</span>, <span class="hljs-number">77</span>, <span class="hljs-number">12</span>, <span class="hljs-number">89</span>, <span class="hljs-number">31</span>, <span class="hljs-number">12</span>)<br />
.collect(<br />
partitioningBy(i -> i < <span class="hljs-number">50</span>,<br />
toSet()<br />
)<br />
);</code></p>
<p>This will produce the following <code>Map</code>:</p>
<p><code class="java hljs">{false=[65, 89, 77], true=[9, 12, 45, 31]}</code></p>
<p>However, unlike <code>groupingBy()</code>, there's no version that allows us to change the type of the <code>Map</code> returned. But it doesn't matter, you only have two keys that you can get with:</p>
<p><code class="java hljs">Set<Integer> lessThan50 = map.get(<span class="hljs-keyword">true</span>);<br />
Set<Integer> moreThan50 = map.get(<span class="hljs-keyword">false</span>);</code></p>
<h2>Key Points</h2>
<ul>
<li><code>peek()</code> executes the provided <code>Consumer</code> and returns a new stream with the same elements of the original one. Most of the time, this method is used for debugging purposes.</li>
<li><code>map()</code> is used to transform the value or the type of the elements of a stream through a provided <code>Function</code>.</li>
<li><code>flatMap()</code> is used to "flatten" or combine the contents of a stream into
another (new) stream. In contrast to <code>map()</code>, <code>flatMap()</code> takes a <code>Function</code> on a <code>Stream</code>
object, per se; whereas <code>map()</code> takes a <code>Function</code> on the objects within its
stream. Both <code>map()</code> and <code>flatMap()</code>return some kind of <code>Stream</code>.</li>
<li>A reduction is an operation that takes many elements and combines them (o reduce them) into a single value or object.</li>
<li><code>reduce()</code> performs a reduction on the elements of a stream using an accumulation function, an optional identity, and an also optional combiner function.</li>
<li><code>collect()</code> implements a type of reduction called <i>mutable reduction</i>, where a container (like a <code>Collection</code>) is used to accumulate the result of the operation.</li>
<li>The <code>Collectors</code> class provides static methods such as <code>toList()</code> and <code>toMap()</code> to create a collection or a map from a stream and some calculation methods like <code>averagingInt()</code>.</li>
<li><code>Collectors.groupingBy()</code> groups the elements of a stream using a given <code>Function</code> as a classifier. It can also receive a <i>downstream collector</i> to create another level of classification.</li>
<li>You can also group (or partition) the elements in a stream based on a condition (<code>Predicate</code>) using the <code>Collectors.partitioningBy()</code> method.</li>
</ul>
<h2>Self Test</h2>
<p>1. Given:</p>
<p><code class="java hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Question_17_1</span></span> {<br />
<span class="hljs-function"><span class="hljs-keyword"> public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span> {<br />
Map<Boolean, List<Integer>> map =<br />
Stream.of(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>)<br />
.collect(partitioningBy(i -> i < <span class="hljs-number">5</span>));<br />
System.out.println(map);<br />
}<br />
}</code></p>
<p>What is the result?<br /> A. <code>{true=[1,2, 3, 4]}</code><br /> B. <code>{false=[], true=[1, 2, 3, 4]}</code><br /> C. <code>{false=[1,2, 3, 4]}</code><br /> D. <code>{false=[1, 2, 3, 4], true=[]}</code></p>
<p>2. Given:</p>
<p><code class="java hljs">groupingBy(i -> i%<span class="hljs-number">3</span>, toList())</code></p>
<p>Which of the following is equivalent?<br /> A. <code>partitioningBy(i -> i%3 == 0, toList())</code><br /> B. <code>partitioningBy(i -> i%3, toList())</code><br /> C. <code>groupingBy(i -> i%3 == 0)</code><br /> D. <code>groupingBy(i -> i%3)</code></p>
<p>3. Given:</p>
<p><code class="java hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Question_17_3</span></span> {<br />
<span class="hljs-function"><span class="hljs-keyword"> public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span> {<br />
Stream.of(<span class="hljs-string">"aaaaa"</span>, <span class="hljs-string">"bbbb"</span>, <span class="hljs-string">"ccc"</span>)<br />
.map(s -> s.split(<span class="hljs-string">""</span>))<br />
.limit(<span class="hljs-number">1</span>)<br />
.forEach(System.out::print);<br />
}<br />
}</code></p>
<p>What is the result?<br /> A. <code>aaaaa</code><br /> B. <code>abc</code><br /> C. <code>a</code><br /> D. None of the above</p>
<p>4. Given:</p>
<p><code class="java hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Question_17_4</span></span> {<br />
<span class="hljs-function"><span class="hljs-keyword"> public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span> {<br />
System.out.println(<br />
Stream.of(<span class="hljs-string">"a"</span>, <span class="hljs-string">"b"</span>, <span class="hljs-string">"c"</span>)<br />
.flatMap(s -> Stream.of(s, s , s))<br />
.collect(Collectors.toList())<br />
);<br />
}<br />
}</code></p>
<p>What is the result?<br /> A. <code>[a, a, a, b, b, b, c, c, c]</code><br /> B. <code>[a, a, a]</code><br /> C. <code>[a, b, c]</code><br /> D. Compilation fails</p>
<p>5. Which of the following is the right way to implement <code>OptionalInt min()</code> with a reduce operation?<br /> A. <code>reduce((a,b) -> a > b)</code><br /> B. <code>reduce(Math::min)</code><br /> C. <code>reduce(Integer.MIN_VALUE, Math:min)</code><br /> D. <code>collect(Collectors.minBy())</code></p>
<p>6. Which of the following is a correct overload of the <code>reduce()</code> method?<br /> A. <code class="hljs">T reduce(BinaryOperator<T> accumulator)</code><br /> B. <code class="hljs">Optional<T> reduce(T identity,<br />
BinaryOperator<T> accumulator)</code><br /> C. <code class="hljs"><U> U reduce(BinaryOperator<T> accumulator,<br />
BinaryOperator<U> combiner)</code><br /> D. <code class="hljs"><U> U reduce(U identity,<br />
BiFunction<U,? super T,U> accumulator,<br />
BinaryOperator<U> combiner)</code></p>
<p>7. Given:</p>
<p><code class="java hljs"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Question_17_7</span></span> {<br />
<span class="hljs-function"><span class="hljs-keyword"> public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span></span> {<br />
Map<Integer, Map<Boolean, List<Integer>>> map =<br />
Stream.of(<span class="hljs-number">56</span>, <span class="hljs-number">54</span>, <span class="hljs-number">1</span>, <span class="hljs-number">31</span>, <span class="hljs-number">98</span>, <span class="hljs-number">98</span>, <span class="hljs-number">16</span>)<br />
.collect(groupingBy(<br />
(Integer i) -> i%<span class="hljs-number">10</span>,<br />
TreeMap::<span class="hljs-keyword">new</span>,<br />
partitioningBy((Integer i) -> i > <span class="hljs-number">5</span>)<br />
)<br />
);<br />
System.out.println(map);<br />
}<br />
}</code></p>
<p>What is the result?<br /> A. <code class="hljs">{<br />
6={false=[], true=[56, 16]},<br />
4={false=[], true=[54]},<br />
1={false=[1], true=[31]},<br />
8={false=[], true=[98]}<br />
}</code><br /> B. <code class="hljs">{<br />
1={false=[1], true=[31]},<br />
4={false=[], true=[54]},<br />
6={false=[], true=[56, 16]},<br />
8={false=[], true=[98]}<br />
}</code><br /> C. <code class="hljs">{<br />
1={false=[1], true=[31]},<br />
4={false=[], true=[54]},<br />
6={false=[], true=[56, 16]},<br />
8={false=[], true=[98, 98]}<br />
}</code><br /> D. <code class="hljs">{<br />
1={false=[1], true=[31]},<br />
4={false=[], true=[54]}<br />
}</code></p>