收集/计数到非空的Map会抛出ClassCastException。

18 浏览
0 Comments

收集/计数到非空的Map会抛出ClassCastException。

我的目标是将List中每个项的计数存储在Map中。可以通过groupingBy()和counting()方法实现这一目标。

我还有一个限制条件,即对于List中不存在的值,我仍然需要为该键提供映射为0的值。因此,必须定义所有可能的值。

这是我想出的代码:

Map EMPTY = Map.of("a", 0L,
                                 "b", 0L,
                                 "c", 0L,
                                 "d", 0L);
List list = List.of("a", "a", "d", "c", "d", "c", "a", "d");
Map count = list.stream()
                              .collect(groupingBy(s -> s,
                                                  () -> new HashMap<>(EMPTY),
                                                  counting()));

这段代码抛出了以下异常:

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)

但是,如果我将`new HashMap<>(EMPTY)`替换为`new HashMap<>()`,代码就可以正常工作。

我没有使用空的Map进行收集过程,是否违反了什么规定?如何使用流来实现我的目标?

0
0 Comments

在使用Java的Stream流进行分组操作时,我们可以使用Collectors类的groupingBy方法。这个方法接受三个参数:classifier、mapFactory和downstream。其中,mapFactory参数是一个供应商,用于提供一个新的空Map来存储结果。

根据官方文档的描述,mapFactory参数应该提供一个非空的Map。然而,在某些情况下,如果我们提供了一个空的Map,就会抛出ClassCastException异常。

这个问题的原因是,Java在内部使用了强制类型转换来将结果插入到提供的Map中。当我们提供一个空的Map时,Java会尝试将结果插入到这个空的Map中,但由于Map是空的,无法进行强制类型转换,从而导致ClassCastException异常的抛出。

为了解决这个问题,我们需要提供一个非空的Map作为mapFactory参数的值。可以使用HashMap、TreeMap等实现了Map接口的类来作为mapFactory参数的值。确保提供的Map是非空的,这样就能够成功地将结果插入到Map中,而不会抛出ClassCastException异常。

以下是一个示例代码,演示了如何正确地使用mapFactory参数来避免ClassCastException异常的发生:

import java.util.*;
import java.util.stream.Collectors;
public class Main {
    public static void main(String[] args) {
        List names = Arrays.asList("Alice", "Bob", "Charlie", "Alice");
        Map> result = names.stream()
                .collect(Collectors.groupingBy(
                        name -> name.substring(0, 1),
                        HashMap::new,
                        Collectors.toList()
                ));
        System.out.println(result);
    }
}

在上面的示例中,我们使用HashMap作为mapFactory参数的值,确保了提供的Map是非空的。这样,我们就能够成功地将结果插入到Map中,并得到正确的分组结果。

总结起来,当使用Collectors的groupingBy方法进行分组操作时,需要注意提供一个非空的Map作为mapFactory参数的值,以避免ClassCastException异常的发生。可以使用HashMap、TreeMap等实现了Map接口的类来作为mapFactory参数的值,确保提供的Map是非空的。这样就能够成功地将结果插入到Map中,得到正确的分组结果。

0
0 Comments

Collecting/Counting into non-empty Map throws ClassCastException问题的原因是在将列表转换为Map时,尝试将计数值添加到已存在的Map中。这样做会导致ClassCastException异常。

解决该问题的方法是,首先创建一个空的Map,然后使用stream和groupingBy将列表分组,并使用counting计算每个元素的数量。然后,使用forEach将计数值更新到已存在的Map中。

代码示例如下:

list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
    .forEach((k, v) -> empty.put(k, v));

注意,EMPTY Map应该是一个静态的final字段,因此不可变。可以使用以下方式来遍历和更新Map中的值:

Set MAPPINGS = Set.of("a", "b", "c", "d");
List list = List.of("a", "a", "d", "c", "d", "c", "a", "d");
Map count = list.stream().collect(groupingBy(s -> s, counting()));
MAPPINGS.forEach(s -> count.putIfAbsent(s, 0L));

这样做可以避免出现ClassCastException异常,具体取决于需求。

0
0 Comments

在使用Collectors.counting时,由于使用的收集器实际上是将元素累积到原始long类型的单个元素数组中,因此会出现一个奇怪的错误。

groupingBy在执行computeIfAbsent时,期望得到的是一个long[],但是因为已经存在键"a",所以返回的是一个Long,而不是accumulator所接受的类型,这就导致了异常的抛出。

稍后,他们使用上面定义的'finisher'(a -> a[0])将整个映射的值替换为Long,将long[]转换为Long。

是的,这有点调皮,但是你违反了合同规定的mapFactory:提供一个新的空Map,将结果插入其中,所以也可以说是公平的。他们使用的是在编译时决定为Map<String,Long>的HashMap,并将long[]放入其中。这是可能的,因为泛型不被具体化。在运行时,它只是一个能够存储任何类型的键和值的HashMap。

对于"where did you get the part about They're taking a HashMap which at compile-time was decided to be Map<String, Long> and they're putting long[]s into it?"的部分,有一些类型参数,如K和A,但是这些参数被推断为String和long[],不太确定你指的是什么。

map工厂的类型是M extends Map<K,D>,它被推断为Map<String, Long>。如果你尝试显式指定long[]给groupingBy,那么它将无法编译。

实际收集元素的代码是BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {...},其中A被推断为long[];有一个finisher将映射到正确的Map类型。

我不认为我能用文字解释得更好了。这里有一个例子。如果取消注释第三个例子,它将无法编译。

在这里已经讨论过这个问题:当finisher函数不是恒等函数时,下游收集器的中间容器类型实际上与结果Map的值类型不兼容。但是,一个类型安全的替代方案需要在完成时复制整个映射,并且还需要一个第二个映射供应商。

0