【问题标题】:Java 8: Collector groupby with list nested classJava 8:具有列表嵌套类的收集器 groupby
【发布时间】:2017-09-15 23:48:54
【问题描述】:

我正在尝试通过 Java 8 获取以下地图

Class One {
    String one;
    List <Two> two;
}

Class Two {
    BigDecimal bd;
}

如何收集包含按 One.one 分组的地图,即地图的第一个参数。对于Two.bd的map sum的第二个参数。

【问题讨论】:

  • 你能澄清一下吗?
  • 你需要更详细地解释你想要什么。

标签: java grouping collectors collect


【解决方案1】:

你可以用这个:

List<One> list = ...;

Map<String, BigDecimal> result1 = list.stream()
    .collect(Collectors.groupingBy(One::getOne, // key is One.one
        Collectors.mapping(one -> one.getTwo().stream() // get stream of One.two
            .map(Two::getBd) // map to Bd
            .reduce(BigDecimal.ZERO, BigDecimal::add), // reduce to sum
            Collectors.reducing(BigDecimal.ZERO, BigDecimal::add) // sum sums
        )
    ));

这将对Two 中的所有bd 求和,然后对具有相同oneOnes 求和。


虽然如果 Java8 有一个 flatMapping 收集器,事情会更简单,但 Java9 已经添加了一个:

public static <T, U, A, R>
Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
                               Collector<? super U, A, R> downstream) {
    BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
    return Collector.of(downstream.supplier(),
                        (r, t) -> mapper.apply(t).sequential().forEach(u -> downstreamAccumulator.accept(r, u)),
                        downstream.combiner(),
                        downstream.finisher(),
                        downstream.characteristics().stream().toArray(Collector.Characteristics[]::new));
}

这会让:

Map<String, BigDecimal> result1 = list.stream()
    .collect(Collectors.groupingBy(One::getOne,
        flatMapping(one -> one.getTwo().stream().map(Two::getBd),
            Collectors.reducing(BigDecimal.ZERO, BigDecimal::add)
        )
    ));

【讨论】:

  • 实际上,您的变体在遇到 One::getOne 的重复值时会引发异常。此处正确的收集器与正确的下游收集器分组,而不是toMap
  • @M.Prokhorov 我们似乎对这个问题的理解不同。我从声明中得出,bd 的总和应该是地图的第二个参数,这是想要的解决方案。但我为您的解释添加了另一种解决方案。
  • @M.Prokhorov 它给出异常 - 线程“Thread-0”java.lang.IllegalStateException 中的异常:java.util.stream.Collectors.lambda$throwingMerger$114 处的重复键 8.002(未知来源)在 java.util.HashMap.merge(Unknown Source) at java.util.stream.Collectors.lambda$toMap$172(Unknown Source) at java.util.stream.ReduceOps$3ReducingSink.accept(Unknown Source) at java.util。 ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source) at java.util.stream.AbstractPipeline.copyInto(Unknown Source) at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
  • @JornVernee Ups,我发布答案时错过了您的编辑。你答案的第一部分是错误的。您可以使用 groupingBy 来做到这一点(如答案的第二部分),或者您也可以通过正确处理 One::getOneCollectors.toMap 的重载版本之一的冲突来做到这一点。
  • @FedericoPeraltaSchaffner 我误解了问题背后的意图,感谢您的反馈。我已经删除了错误的版本。
【解决方案2】:

这会做你想做的事:

Map<String, BigDecimal> map = ones.stream()
    .collect(Collectors.groupingBy(
        One::getOne,
        Collectors.mapping(
            one -> one.getTwo().stream()
                .map(Two::getBd)
                .reduce(BigDecimal.ZERO, BigDecimal::add),
            Collectors.reducing(
                BigDecimal.ZERO,
                BigDecimal::add))));

这由One.one 收集,将每个One.two 转换为BigDecimal,即其Two.bd 属性的总和,然后在列表中重复One.one 的情况下再次减少和求和。

编辑:

@Jorn Vernee 编辑的答案的第一部分与我自己的完全相同,所以在这里我提出另一种方法:

Map<String, BigDecimal> map = ones.stream()
    .collect(Collectors.toMap(
        One::getOne,
        one -> one.getTwo().stream()
            .map(Two::getBd)
            .reduce(BigDecimal.ZERO, BigDecimal::add),
        BigDecimal::add));

这里我使用Collectors.toMap 的重载版本,它通过使用提供的合并函数(在本例中为BigDecimal::add)来处理冲突。

【讨论】:

  • 我还是更喜欢groupingBy,因为它比toMap更能显示意图。但是在你的第二个 sn-p 中是个好主意。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多