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.
We can use a flatMap() method on a stream with the mapper function List::stream. On executing the stream terminal operation, each element of flatMap() provides a separate stream. In the final phase, the flatMap() method transforms all the streams into a new stream.
The difference is that the map operation produces one output value for each input value, whereas the flatMap operation produces an arbitrary number (zero or more) values for each input value.
Does flatmap() method preserve the order of the streams? Yes, It does and map() also.
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())));
}
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))
)
));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With