My goal is to store the count of each item of a List inside a Map. This can be achieved by the groupingBy()
and counting()
methods.
The next constraint I have, is that for a value not in the List I would still need a mapping for that key to be 0. Thus all possible values must be defined.
Here's what I've come up with:
Map<String, Long> EMPTY = Map.of("a", 0L,
"b", 0L,
"c", 0L,
"d", 0L);
List<String> list = List.of("a", "a", "d", "c", "d", "c", "a", "d");
Map<String, Long> count = list.stream()
.collect(groupingBy(s -> s,
() -> new HashMap<>(EMPTY),
counting()));
This code throws following exception:
Exception in thread "main" java.lang.ClassCastException: class java.lang.Long cannot be cast to class [J (java.lang.Long and [J are in module java.base of loader 'bootstrap')
at java.base/java.util.stream.Collectors.lambda$groupingBy$53(Collectors.java:1129)
at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at Test.main(Test.java:18)
But if I just replace new HashMap<>(EMPTY)
with new HashMap<>()
the code works fine.
Am I violating something by not using an empty Map for the collect process? How would I otherwise achieve my goal using streams?
It's a bit of a weird error. Specifically, the collector that you're using (by virtue of using Collectors.counting
) is actually accumulating into single element arrays of primitive long
s.
public static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper)
{
return new CollectorImpl<>(
() -> new long[1],
(a, t) -> { a[0] += mapper.applyAsLong(t); },
(a, b) -> { a[0] += b[0]; return a; },
a -> a[0], CH_NOID);
}
When groupingBy
does a computeIfAbsent
, it's expecting to get a long[]
but because you already have a key for "a", you get back a Long
which doesn't match the type that's accepted by the accumulator. That's what throws the exception.
A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
downstreamAccumulator.accept(container, t);
Later on, they replace all of the map values:
intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
using the 'finisher' defined above (a -> a[0]
) to go from long[]
s to Long
s.
Yes, it's a little bit naughty but you've violated the contract
mapFactory: a supplier providing a new empty Map into which the results will be inserted
so it's also sort of fair enough. They're taking a HashMap
which at compile-time was decided to be Map<String, Long>
and they're putting long[]
s into it. That's possible because generics are not reified. At runtime, it's simply a HashMap
capable of storing any types of keys and values.