6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23 package org.openjdk.tests.java.util.stream;
24
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.TreeMap;
34 import java.util.concurrent.ConcurrentHashMap;
35 import java.util.concurrent.ConcurrentSkipListMap;
36 import java.util.function.BinaryOperator;
37 import java.util.function.Function;
38 import java.util.function.Predicate;
39 import java.util.function.Supplier;
40 import java.util.stream.Collector;
41 import java.util.stream.Collectors;
42 import java.util.stream.LambdaTestHelpers;
43 import java.util.stream.OpTestCase;
44 import java.util.stream.Stream;
45 import java.util.stream.StreamOpFlagTestHelper;
46 import java.util.stream.StreamTestDataProvider;
47 import java.util.stream.TestData;
48
49 import org.testng.annotations.Test;
50
51 import static java.util.stream.Collectors.groupingBy;
52 import static java.util.stream.Collectors.groupingByConcurrent;
53 import static java.util.stream.Collectors.partitioningBy;
54 import static java.util.stream.Collectors.reducing;
55 import static java.util.stream.Collectors.toCollection;
56 import static java.util.stream.Collectors.toList;
57 import static java.util.stream.LambdaTestHelpers.assertContents;
58 import static java.util.stream.LambdaTestHelpers.assertContentsUnordered;
59 import static java.util.stream.LambdaTestHelpers.mDoubler;
60
61 /**
62 * TabulatorsTest
63 *
64 * @author Brian Goetz
65 */
66 @SuppressWarnings({"rawtypes", "unchecked"})
67 public class TabulatorsTest extends OpTestCase {
68 // There are 8 versions of groupingBy:
69 // groupingBy: { map supplier, not } x { downstream collector, not } x { concurrent, not }
70 // There are 2 versions of partition: { map supplier, not }
71 // There are 4 versions of toMap
72 // mappedTo(function, mapSupplier?, mergeFunction?)
73 // Each variety needs at least one test
74 // Plus a variety of multi-level tests (groupBy(..., partition), partition(..., groupBy))
75 // Plus negative tests for mapping to null
76 // Each test should be matched by a nest of asserters (see TabulationAssertion...)
77
78
79 private static abstract class TabulationAssertion<T, U> {
80 abstract void assertValue(U value,
81 Supplier<Stream<T>> source,
82 boolean ordered) throws ReflectiveOperationException;
83 }
84
85 @SuppressWarnings({"rawtypes", "unchecked"})
86 static class GroupedMapAssertion<T, K, V, M extends Map<K, ? extends V>> extends TabulationAssertion<T, M> {
87 private final Class<? extends Map> clazz;
88 private final Function<T, K> classifier;
89 private final TabulationAssertion<T,V> downstream;
90
91 protected GroupedMapAssertion(Function<T, K> classifier,
92 Class<? extends Map> clazz,
93 TabulationAssertion<T, V> downstream) {
94 this.clazz = clazz;
95 this.classifier = classifier;
96 this.downstream = downstream;
97 }
98
99 void assertValue(M map,
100 Supplier<Stream<T>> source,
101 boolean ordered) throws ReflectiveOperationException {
102 if (!clazz.isAssignableFrom(map.getClass()))
103 fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass()));
104 assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(Collectors.toSet()));
105 for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
106 K key = entry.getKey();
107 downstream.assertValue(entry.getValue(),
108 () -> source.get().filter(e -> classifier.apply(e).equals(key)),
109 ordered);
110 }
111 }
112 }
113
114 static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> {
115 private final Predicate<T> predicate;
116 private final TabulationAssertion<T,D> downstream;
117
118 protected PartitionAssertion(Predicate<T> predicate,
119 TabulationAssertion<T, D> downstream) {
120 this.predicate = predicate;
121 this.downstream = downstream;
122 }
123
124 void assertValue(Map<Boolean, D> map,
125 Supplier<Stream<T>> source,
126 boolean ordered) throws ReflectiveOperationException {
127 if (!Map.class.isAssignableFrom(map.getClass()))
128 fail(String.format("Class mismatch in PartitionAssertion: %s", map.getClass()));
129 assertEquals(2, map.size());
130 downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered);
131 downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered);
132 }
133 }
187 this.reducer = reducer;
188 }
189
190 @Override
191 void assertValue(U value, Supplier<Stream<T>> source, boolean ordered)
192 throws ReflectiveOperationException {
193 Optional<U> reduced = source.get().map(mapper).reduce(reducer);
194 if (value == null)
195 assertTrue(!reduced.isPresent());
196 else if (!reduced.isPresent()) {
197 assertEquals(value, identity);
198 }
199 else {
200 assertEquals(value, reduced.get());
201 }
202 }
203 }
204
205 private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
206 return (act, exp, ord, par) -> {
207 if (par & (!ordered || !ord)) {
208 TabulatorsTest.nestedMapEqualityAssertion(act, exp);
209 }
210 else {
211 LambdaTestHelpers.assertContentsEqual(act, exp);
212 }
213 };
214 }
215
216 private<T, M extends Map>
217 void exerciseMapTabulation(TestData<T, Stream<T>> data,
218 Collector<T, ? extends M> collector,
219 TabulationAssertion<T, M> assertion)
220 throws ReflectiveOperationException {
221 boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
222
223 M m = withData(data)
224 .terminal(s -> s.collect(collector))
225 .resultAsserter(mapTabulationAsserter(ordered))
226 .exercise();
227 assertion.assertValue(m, () -> data.stream(), ordered);
228
229 m = withData(data)
230 .terminal(s -> s.unordered().collect(collector))
231 .resultAsserter(mapTabulationAsserter(ordered))
232 .exercise();
233 assertion.assertValue(m, () -> data.stream(), false);
234 }
235
236 private static void nestedMapEqualityAssertion(Object o1, Object o2) {
237 if (o1 instanceof Map) {
238 Map m1 = (Map) o1;
239 Map m2 = (Map) o2;
240 assertContentsUnordered(m1.keySet(), m2.keySet());
241 for (Object k : m1.keySet())
242 nestedMapEqualityAssertion(m1.get(k), m2.get(k));
243 }
244 else if (o1 instanceof Collection) {
245 assertContentsUnordered(((Collection) o1), ((Collection) o2));
246 }
247 else
248 assertEquals(o1, o2);
249 }
250
251 @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
252 public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
253 Function<Integer, Integer> classifier = i -> i % 3;
254
255 // Single-level groupBy
256 exerciseMapTabulation(data, groupingBy(classifier),
257 new GroupedMapAssertion<>(classifier, HashMap.class,
258 new ListAssertion<>()));
259 exerciseMapTabulation(data, groupingByConcurrent(classifier),
260 new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
261 new ListAssertion<>()));
262
263 // With explicit constructors
264 exerciseMapTabulation(data,
265 groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)),
266 new GroupedMapAssertion<>(classifier, TreeMap.class,
267 new CollectionAssertion<Integer>(HashSet.class, false)));
268 exerciseMapTabulation(data,
|
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23 package org.openjdk.tests.java.util.stream;
24
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Comparator;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Optional;
35 import java.util.Set;
36 import java.util.TreeMap;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.ConcurrentSkipListMap;
39 import java.util.function.BinaryOperator;
40 import java.util.function.Function;
41 import java.util.function.Predicate;
42 import java.util.function.Supplier;
43 import java.util.stream.Collector;
44 import java.util.stream.Collectors;
45 import java.util.stream.LambdaTestHelpers;
46 import java.util.stream.OpTestCase;
47 import java.util.stream.Stream;
48 import java.util.stream.StreamOpFlagTestHelper;
49 import java.util.stream.StreamTestDataProvider;
50 import java.util.stream.TestData;
51
52 import org.testng.annotations.Test;
53
54 import static java.util.stream.Collectors.groupingBy;
55 import static java.util.stream.Collectors.groupingByConcurrent;
56 import static java.util.stream.Collectors.partitioningBy;
57 import static java.util.stream.Collectors.reducing;
58 import static java.util.stream.Collectors.toCollection;
59 import static java.util.stream.Collectors.toConcurrentMap;
60 import static java.util.stream.Collectors.toList;
61 import static java.util.stream.Collectors.toMap;
62 import static java.util.stream.Collectors.toSet;
63 import static java.util.stream.LambdaTestHelpers.assertContents;
64 import static java.util.stream.LambdaTestHelpers.assertContentsUnordered;
65 import static java.util.stream.LambdaTestHelpers.mDoubler;
66
67 /**
68 * TabulatorsTest
69 *
70 * @author Brian Goetz
71 */
72 @SuppressWarnings({"rawtypes", "unchecked"})
73 public class TabulatorsTest extends OpTestCase {
74
75 private static abstract class TabulationAssertion<T, U> {
76 abstract void assertValue(U value,
77 Supplier<Stream<T>> source,
78 boolean ordered) throws ReflectiveOperationException;
79 }
80
81 @SuppressWarnings({"rawtypes", "unchecked"})
82 static class GroupedMapAssertion<T, K, V, M extends Map<K, ? extends V>> extends TabulationAssertion<T, M> {
83 private final Class<? extends Map> clazz;
84 private final Function<T, K> classifier;
85 private final TabulationAssertion<T,V> downstream;
86
87 protected GroupedMapAssertion(Function<T, K> classifier,
88 Class<? extends Map> clazz,
89 TabulationAssertion<T, V> downstream) {
90 this.clazz = clazz;
91 this.classifier = classifier;
92 this.downstream = downstream;
93 }
94
95 void assertValue(M map,
96 Supplier<Stream<T>> source,
97 boolean ordered) throws ReflectiveOperationException {
98 if (!clazz.isAssignableFrom(map.getClass()))
99 fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass()));
100 assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(toSet()));
101 for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
102 K key = entry.getKey();
103 downstream.assertValue(entry.getValue(),
104 () -> source.get().filter(e -> classifier.apply(e).equals(key)),
105 ordered);
106 }
107 }
108 }
109
110 static class ToMapAssertion<T, K, V, M extends Map<K,V>> extends TabulationAssertion<T, M> {
111 private final Class<? extends Map> clazz;
112 private final Function<T, K> keyFn;
113 private final Function<T, V> valueFn;
114 private final BinaryOperator<V> mergeFn;
115
116 ToMapAssertion(Function<T, K> keyFn,
117 Function<T, V> valueFn,
118 BinaryOperator<V> mergeFn,
119 Class<? extends Map> clazz) {
120 this.clazz = clazz;
121 this.keyFn = keyFn;
122 this.valueFn = valueFn;
123 this.mergeFn = mergeFn;
124 }
125
126 @Override
127 void assertValue(M map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
128 Set<K> uniqueKeys = source.get().map(keyFn).collect(toSet());
129 assertTrue(clazz.isAssignableFrom(map.getClass()));
130 assertEquals(uniqueKeys, map.keySet());
131 source.get().forEach(t -> {
132 K key = keyFn.apply(t);
133 V v = source.get()
134 .filter(e -> key.equals(keyFn.apply(e)))
135 .map(valueFn)
136 .reduce(mergeFn)
137 .get();
138 assertEquals(map.get(key), v);
139 });
140 }
141 }
142
143 static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> {
144 private final Predicate<T> predicate;
145 private final TabulationAssertion<T,D> downstream;
146
147 protected PartitionAssertion(Predicate<T> predicate,
148 TabulationAssertion<T, D> downstream) {
149 this.predicate = predicate;
150 this.downstream = downstream;
151 }
152
153 void assertValue(Map<Boolean, D> map,
154 Supplier<Stream<T>> source,
155 boolean ordered) throws ReflectiveOperationException {
156 if (!Map.class.isAssignableFrom(map.getClass()))
157 fail(String.format("Class mismatch in PartitionAssertion: %s", map.getClass()));
158 assertEquals(2, map.size());
159 downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered);
160 downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered);
161 }
162 }
216 this.reducer = reducer;
217 }
218
219 @Override
220 void assertValue(U value, Supplier<Stream<T>> source, boolean ordered)
221 throws ReflectiveOperationException {
222 Optional<U> reduced = source.get().map(mapper).reduce(reducer);
223 if (value == null)
224 assertTrue(!reduced.isPresent());
225 else if (!reduced.isPresent()) {
226 assertEquals(value, identity);
227 }
228 else {
229 assertEquals(value, reduced.get());
230 }
231 }
232 }
233
234 private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
235 return (act, exp, ord, par) -> {
236 if (par && (!ordered || !ord)) {
237 TabulatorsTest.nestedMapEqualityAssertion(act, exp);
238 }
239 else {
240 LambdaTestHelpers.assertContentsEqual(act, exp);
241 }
242 };
243 }
244
245 private<T, M extends Map>
246 void exerciseMapTabulation(TestData<T, Stream<T>> data,
247 Collector<T, ?, ? extends M> collector,
248 TabulationAssertion<T, M> assertion)
249 throws ReflectiveOperationException {
250 boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
251
252 M m = withData(data)
253 .terminal(s -> s.collect(collector))
254 .resultAsserter(mapTabulationAsserter(ordered))
255 .exercise();
256 assertion.assertValue(m, () -> data.stream(), ordered);
257
258 m = withData(data)
259 .terminal(s -> s.unordered().collect(collector))
260 .resultAsserter(mapTabulationAsserter(ordered))
261 .exercise();
262 assertion.assertValue(m, () -> data.stream(), false);
263 }
264
265 private static void nestedMapEqualityAssertion(Object o1, Object o2) {
266 if (o1 instanceof Map) {
267 Map m1 = (Map) o1;
268 Map m2 = (Map) o2;
269 assertContentsUnordered(m1.keySet(), m2.keySet());
270 for (Object k : m1.keySet())
271 nestedMapEqualityAssertion(m1.get(k), m2.get(k));
272 }
273 else if (o1 instanceof Collection) {
274 assertContentsUnordered(((Collection) o1), ((Collection) o2));
275 }
276 else
277 assertEquals(o1, o2);
278 }
279
280 private<T, R> void assertCollect(TestData.OfRef<T> data,
281 Collector<T, ?, R> collector,
282 Function<Stream<T>, R> streamReduction) {
283 R check = streamReduction.apply(data.stream());
284 withData(data).terminal(s -> s.collect(collector)).expectedResult(check).exercise();
285 }
286
287 @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
288 public void testReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
289 assertCollect(data, Collectors.reducing(0, Integer::sum),
290 s -> s.reduce(0, Integer::sum));
291 assertCollect(data, Collectors.reducing(Integer.MAX_VALUE, Integer::min),
292 s -> s.min(Integer::compare).orElse(Integer.MAX_VALUE));
293 assertCollect(data, Collectors.reducing(Integer.MIN_VALUE, Integer::max),
294 s -> s.max(Integer::compare).orElse(Integer.MIN_VALUE));
295
296 assertCollect(data, Collectors.reducing(Integer::sum),
297 s -> s.reduce(Integer::sum));
298 assertCollect(data, Collectors.minBy(Comparator.naturalOrder()),
299 s -> s.min(Integer::compare));
300 assertCollect(data, Collectors.maxBy(Comparator.naturalOrder()),
301 s -> s.max(Integer::compare));
302
303 assertCollect(data, Collectors.reducing(0, x -> x*2, Integer::sum),
304 s -> s.map(x -> x*2).reduce(0, Integer::sum));
305
306 assertCollect(data, Collectors.summingLong(x -> x * 2L),
307 s -> s.map(x -> x*2L).reduce(0L, Long::sum));
308 assertCollect(data, Collectors.summingInt(x -> x * 2),
309 s -> s.map(x -> x*2).reduce(0, Integer::sum));
310 assertCollect(data, Collectors.summingDouble(x -> x * 2.0d),
311 s -> s.map(x -> x * 2.0d).reduce(0.0d, Double::sum));
312
313 assertCollect(data, Collectors.averagingInt(x -> x * 2),
314 s -> s.mapToInt(x -> x * 2).average().orElse(0));
315 assertCollect(data, Collectors.averagingLong(x -> x * 2),
316 s -> s.mapToLong(x -> x * 2).average().orElse(0));
317 assertCollect(data, Collectors.averagingDouble(x -> x * 2),
318 s -> s.mapToDouble(x -> x * 2).average().orElse(0));
319
320 // Test explicit Collector.of
321 Collector<Integer, long[], Double> avg2xint = Collector.of(() -> new long[2],
322 (a, b) -> {
323 a[0] += b * 2;
324 a[1]++;
325 },
326 (a, b) -> {
327 a[0] += b[0];
328 a[1] += b[1];
329 return a;
330 },
331 a -> a[1] == 0 ? 0.0d : (double) a[0] / a[1]);
332 assertCollect(data, avg2xint,
333 s -> s.mapToInt(x -> x * 2).average().orElse(0));
334 }
335
336 @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
337 public void testJoin(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
338 withData(data)
339 .terminal(s -> s.map(Object::toString).collect(Collectors.joining()))
340 .expectedResult(join(data, ""))
341 .exercise();
342
343 Collector<String, StringBuilder, String> likeJoining = Collector.of(StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2.toString()), StringBuilder::toString);
344 withData(data)
345 .terminal(s -> s.map(Object::toString).collect(likeJoining))
346 .expectedResult(join(data, ""))
347 .exercise();
348
349 withData(data)
350 .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",")))
351 .expectedResult(join(data, ","))
352 .exercise();
353
354 withData(data)
355 .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",", "[", "]")))
356 .expectedResult("[" + join(data, ",") + "]")
357 .exercise();
358 }
359
360 private<T> String join(TestData.OfRef<T> data, String delim) {
361 StringBuilder sb = new StringBuilder();
362 boolean first = true;
363 for (T i : data) {
364 if (!first)
365 sb.append(delim);
366 sb.append(i.toString());
367 first = false;
368 }
369 return sb.toString();
370 }
371
372 @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
373 public void testSimpleToMap(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
374 Function<Integer, Integer> keyFn = i -> i * 2;
375 Function<Integer, Integer> valueFn = i -> i * 4;
376
377 List<Integer> dataAsList = Arrays.asList(data.stream().toArray(Integer[]::new));
378 Set<Integer> dataAsSet = new HashSet<>(dataAsList);
379
380 BinaryOperator<Integer> sum = Integer::sum;
381 for (BinaryOperator<Integer> op : Arrays.asList((u, v) -> u,
382 (u, v) -> v,
383 sum)) {
384 try {
385 exerciseMapTabulation(data, toMap(keyFn, valueFn),
386 new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
387 if (dataAsList.size() != dataAsSet.size())
388 fail("Expected ISE on input with duplicates");
389 }
390 catch (IllegalStateException e) {
391 if (dataAsList.size() == dataAsSet.size())
392 fail("Expected no ISE on input without duplicates");
393 }
394
395 exerciseMapTabulation(data, toMap(keyFn, valueFn, op),
396 new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
397
398 exerciseMapTabulation(data, toMap(keyFn, valueFn, op, TreeMap::new),
399 new ToMapAssertion<>(keyFn, valueFn, op, TreeMap.class));
400 }
401
402 // For concurrent maps, only use commutative merge functions
403 try {
404 exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn),
405 new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
406 if (dataAsList.size() != dataAsSet.size())
407 fail("Expected ISE on input with duplicates");
408 }
409 catch (IllegalStateException e) {
410 if (dataAsList.size() == dataAsSet.size())
411 fail("Expected no ISE on input without duplicates");
412 }
413
414 exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum),
415 new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
416
417 exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum, ConcurrentSkipListMap::new),
418 new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentSkipListMap.class));
419 }
420
421 @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
422 public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
423 Function<Integer, Integer> classifier = i -> i % 3;
424
425 // Single-level groupBy
426 exerciseMapTabulation(data, groupingBy(classifier),
427 new GroupedMapAssertion<>(classifier, HashMap.class,
428 new ListAssertion<>()));
429 exerciseMapTabulation(data, groupingByConcurrent(classifier),
430 new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
431 new ListAssertion<>()));
432
433 // With explicit constructors
434 exerciseMapTabulation(data,
435 groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)),
436 new GroupedMapAssertion<>(classifier, TreeMap.class,
437 new CollectionAssertion<Integer>(HashSet.class, false)));
438 exerciseMapTabulation(data,
|