问题
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 One
s 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