Java 8: Collector groupby with list nested class

半世苍凉 提交于 2019-12-25 16:55:13

问题


I am trying to get following Map via Java 8

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

Class Two {
    BigDecimal bd;
}

How do I collect a Map which contains grouping by One.one i.e., first parameter of map. For second parameter of map sum of Two.bd.


回答1:


You can use this:

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
        )
    ));

This will sum all the bd of Two, and then also sum the sums for Ones that have the same one.


Although things would be simpler if Java8 had a flatMapping collector, one has been added to 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));
}

Which would make:

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)
        )
    ));



回答2:


This will do what you want:

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))));

This collects by One.one, transforming each One.two to a BigDecimal that is the sum of its Two.bd attributes, then it reduces and sums again in case there are repeated One.one in the list.

EDIT:

First part of @Jorn Vernee's edited answer is exactly the same as my own's, so here I present another approach:

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));

Here I'm using the overloaded version of Collectors.toMap, which takes care of collisions by using the supplied merge function (in this case BigDecimal::add).



来源:https://stackoverflow.com/questions/43500966/java-8-collector-groupby-with-list-nested-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!