我有一个由Map<Pair<DateTime, String>, List<Entity>>组成的集合,该集合以前是使用流进行分组的。 Entity是具有int属性和getValue()方法的简单类。

现在,我想使用简单的Entity来汇总EntityAccumulator的值,并将先前映射的类型修改为Map<Pair<DateTime, String>, EntityAccumulator>。据我所知,实现此目标的唯一方法是创建自己的自定义收集器,但是我坚持使用finisher()方法,该方法应返回Pair

或者,也许有更简单的方法来达到我想要的结果?

StreamProcessing

 Map<Pair<DateTime, String>, EntityAccumulator> collect = entities.stream()
                .collect(Collectors.groupingBy(entity-> Pair.of(entity.getTimestamp(), entity.getName())))
                .entrySet().stream()
                .collect(new EntityCollector()));

EntityAccumulator
private static class EntityAccumulator {

        private int result = 0.0;

        public EntityAccumulator() { }

        public EntityAccumulator(int result) {
            this.result = result;
        }

        public void calculate(Entity entity) {
            result += entity.getValue();
        }

        public EntityAccumulatoradd(EntityAccumulator other) {
            return new EntityAccumulator(this.result + other.result);
        }
}

收集器
public class EntityCollector implements Collector<Map.Entry<Pair<DateTime, String>, List<Entity>>, EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> {

    @Override
    public Supplier<EntityAccumulator> supplier() {
        return EntityAccumulator::new;
    }

    @Override
    public BiConsumer<EntityAccumulator, Map.Entry<Pair<DateTime, String>, List<Entity>>> accumulator() {
        return (result, pairListEntry) -> pairListEntry.getValue().forEach(result::calculate);
    }

    @Override
    public BinaryOperator<EntityAccumulator> combiner() {
        return EntityAccumulator::add;
    }

    @Override
    public Function<EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> finisher() {
        return (k) -> {
            return  null; // ??? HELP HERE
        }
    }


    @Override
    public Set<Characteristics> characteristics() {
        return EnumSet.of(Characteristics.UNORDERED);
    }
}

最佳答案

显然,您实际上想做

Map<Pair<DateTime, String>, Double> collect = entities.stream()
    .collect(Collectors.groupingBy(
        entity -> Pair.of(entity.getTimestamp(), entity.getName()),
        Collectors.summingDouble(Entity::getValue)));

要么
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
    .collect(Collectors.groupingBy(
        entity -> Pair.of(entity.getTimestamp(), entity.getName()),
        Collectors.summingInt(Entity::getValue)));

取决于实际值类型。您的声明int result = 0.0不太清楚。

首先,如果要对组进行归约,则应将值的Collector提供为 groupingBy collector的第二个参数。然后,无需处理MapMap.Entry

由于基本上是将实体折叠成一个单一的数字(对于每个组),因此您可以使用现有的收集器,即 summingInt summingDouble

当您创建自己的收集器时,您无法重构已添加到累加器功能中的排纸处理器功能中的信息。如果您的容器类型EntityAccumulator仅包含一个数字,则无法从中产生一个Map.Entry<Pair<DateTime, String>, EntityAccumulator>

顺便说一句,即使在创建自定义收集器时,也很少需要使用类来实现Collector接口。您可以简单地使用 Collector.of (指定功能和特征)来创建Collector

因此,使用原始的EntityAccumulator类(假设result应该是int,而0.0是一个错字),则可以使用
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
    .collect(Collectors.groupingBy(
        entity -> Pair.of(entity.getTimestamp(), entity.getName()),
        Collector.of(EntityAccumulator::new,
                     EntityAccumulator::calculate,
                     EntityAccumulator::add,
                     ea -> ea.result,
                     Collector.Characteristics.UNORDERED)));

达到与上述相同的效果。也可以分两步执行操作,例如您尝试使用
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
    .collect(Collectors.groupingBy(e -> Pair.of(e.getTimestamp(), e.getName())))
    .entrySet().stream()
    .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().collect(
        Collector.of(EntityAccumulator::new,
                     EntityAccumulator::calculate,
                     EntityAccumulator::add,
                     ea -> ea.result,
                     Collector.Characteristics.UNORDERED))));

但这当然只是为了完整性。此答案开头显示的解决方案更简单,更有效。

10-05 23:34