对流的元素进行计数

2022-09-03 15:39:36

我想数一的不同元素,我想知道为什么

Stream<String> stream = Stream.of("a", "b", "a", "c", "c", "a", "a", "d");
Map<String, Integer> counter1 = stream.collect(Collectors.toMap(s -> s, 1, Integer::sum));

不起作用。日食告诉我

类型收集器中的方法 toMap(Function, Function, BinaryOperator) 不适用于参数 (( s) -> {}, int, Integer::sum)

顺便说一句,我知道这个解决方案:

Map<String, Long> counter2 = stream.collect(Collectors.groupingBy(s -> s, Collectors.counting()));

所以我有两个问题:

  1. 我的第一种方法有什么错误?
  2. 您将如何实现这样的计数器?

编辑:我自己解决了第一个问题:

Map<String, Integer> counter1 = stream.collect(Collectors.toMap(s -> s, s -> 1, Integer::sum)); 

Java 期望将函数作为第二个参数。


答案 1

确实有几种方法可以做到这一点。你没有提到的是.collect(groupingBy(x -> x, summingInt(x -> 1)));

在性能方面存在一些差异。

如果每个存储桶的对象很少,则方法#1将处于最佳状态。在理想情况下,每个存储桶只有 1 个对象,您最终会立即获得最终映射,而无需修改条目。在拥有大量重复对象的最坏情况下,它将不得不进行大量的装箱/拆箱。

方法#2依赖于收集器,它没有确切指定它应该如何进行计数。当前的实现将前进,但这可能会改变。counting()reducing

该方法将累积计数而不是,因此不需要任何装箱/取消装箱。如果对象重复大量次,它将处于最佳状态。summingIntintInteger

至于选择哪一个,最好编写代码以保持清晰,并在必要时进行优化。对我来说,最清楚地表达了意图,所以这是我更喜欢的。groupingBy(x->x, counting())


答案 2
    private Collector<CustomObject.class, int[], Integer> customIntegerCountingCollector() {
    return Collector.of(
        () -> new int[1],
        (result, ) -> result[0] += 1,
        (result1, result2) -> {
            result1[0] += result2[0];
            return result1;
        },
        total -> Integer.valueOf(total[0])
    );
}

灵感来自 : https://www.deadcoderising.com/2017-03-07-java-8-creating-a-custom-collector-for-your-stream/


推荐