The lambda expression in flatMap
needs to return a Stream
, as can be seen by the argument of flatMap
which is of type Function<? super T, ? extends Stream<? extends R>>
.
The following code will compile and run fine:
listOfStrings.stream()
.flatMap(str -> duplicate(str).stream()) // note the .stream() here
.collect(Collectors.toList());
because the lambda expression str -> duplicate(str).stream()
is of type Function<String, Stream<String>>
.
I would just backport flatMapping
. It only requires 2 methods and 1 class, with no other dependencies.
Also, when it comes time to upgrade to Java 9, you can just deprecate your version and replace any usages of it with the proper version.
The following code is taken from the JDK. I didn't write it. I have tested it with your example and it returns the same result.
class Nikollectors
{
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 new CollectorImpl<>(downstream.supplier(),
(r, t) -> {
try (Stream<? extends U> result = mapper.apply(t)) {
if (result != null)
result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
}
},
downstream.combiner(), downstream.finisher(),
downstream.characteristics());
}
private static class CollectorImpl<T, A, R> implements Collector<T, A, R>
{
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A,R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
@Override
public BiConsumer<A, T> accumulator() {
return accumulator;
}
@Override
public Supplier<A> supplier() {
return supplier;
}
@Override
public BinaryOperator<A> combiner() {
return combiner;
}
@Override
public Function<A, R> finisher() {
return finisher;
}
@Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}
private static <I, R> Function<I, R> castingIdentity() {
return i -> (R) i;
}
}
Sample usage:
Map<Integer, List<Integer>> map =list.stream()
.collect(Collectors.groupingBy(
Collection::size,
Nikollectors.flatMapping( // <<<
l -> l.stream().filter(i -> i % 2 == 0),
Collectors.toList()
)
)
);
Best Answer
A technical reason, which is not ideal but could be why this wasn't done. You can't overload on a generic type in Java.
They need to support
which means they can't overload it with
as these two methods have the same signature after erasure.
They could add a method
or
but it wouldn't be pretty.