Reputation: 341
I would like to understand a way to perform a flatMap
while using Collector
s. Here is an example.
Scenario:
I have the following interfaces:
interface Ic {
//empty
}
interface Ib {
Stream<Ic> getCs();
}
interface Ia {
String getName();
Stream<Ib> getBs();
}
And I'm trying to implement the following method:
Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
return streamOfA.collect(groupBy(Ia::getName, ???));
}
The classification function is pretty straitforward, my problem is with the downstream collector. I need to count the number of "C" associated with "A".
What I tried to to:
If I wanted to simply return the count, without creating a map, I would do:
streamOfA
.flatMap(Ia::getBs)
.flatMap(Ib::getCs)
.count();
But the Collectors
class only allows me to do mapping operations. What else can I try to do?
Thanks.
Upvotes: 5
Views: 762
Reputation: 30686
the documentation described the Collectors#mapping as:
Adapts a Collector accepting elements of type
U
to one accepting elements of typeT
by applying a mapping function to each input element before accumulation.The
mapping()
collectors are most useful when used in a multi-level reduction, such as downstream of agroupingBy
orpartitioningBy
.
which means you can composing any possible Collector
s as you can.
import static java.util.stream.Collectors.*;
Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
return streamOfA.collect(groupingBy(
Ia::getName,
mapping(
Ia::getBs,
mapping(
it -> it.flatMap(Ib::getCs),
// reduce() does boxing & unboxing ---v
mapping(Stream::count, reducing(0L,Long::sum))
)
)
));
}
OR using Collectors#summingLong instead.
Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
return streamOfA.collect(groupingBy(
Ia::getName,
mapping(
Ia::getBs,
mapping(
it -> it.flatMap(Ib::getCs),
// summingLong() does boxing ---v
mapping(Stream::count, summingLong(Long::longValue))
// Long::longValue does unboxing operation ---^
)
)
));
}
thanks for @Holger point out the potential problem of the code above, that you can simply using summingLong(Stream::count)
instead. in this approach is no need to boxing Stream#count
which return a long
to a Long
. and Long::longValue
unboxing a Long
to long
.
Map<String, Long> total_of_C_per_A(Stream<Ia> streamOfA) {
return streamOfA.collect(groupingBy(
Ia::getName,
mapping(
Ia::getBs,
// summingLong() doesn't any boxing ---v
mapping(it -> it.flatMap(Ib::getCs), summingLong(Stream::count))
)
));
}
Upvotes: 5
Reputation: 298153
This answer points you already into the right direction, but there is no need to nest multiple mapping
collectors, as you can just write these functions into a single lambda expression. Considering that the summingLong
collector expects a function which evaluates to long
, you can simply pass that function to the collector without any mapping
collector at all:
Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
return streamOfA.collect(groupingBy(
Ia::getName,
summingLong(ia -> ia.getBs().flatMap(Ib::getCs).count())));
}
This also has the advantage that the long
values are not boxed to Long
instances.
There’s also an alternative to flatMap
:
Map<String, Long> total_of_C_per_A (Stream<Ia> streamOfA) {
return streamOfA.collect(groupingBy(
Ia::getName,
summingLong(ia -> ia.getBs().mapToLong(ib -> ib.getCs().count()).sum())));
}
Upvotes: 6