Collecting/Counting into non-empty Map throws ClassCastException

jippiee :

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?

Michael :

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 longs.

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 Longs.


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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=76409&siteId=1