Java 8 Stream API – Rewrite Java 9 Collectors.flatMapping in Java 8

collectorsjavajava-8java-9java-stream

I got in touch with a new feature since called Collectors.flatMapping that takes place as a downstream of grouping or partitioning. Such as (example taken from here):

List<List<Integer>> list = Arrays.asList(
    Arrays.asList(1, 2, 3, 4, 5, 6), 
    Arrays.asList(7, 8, 9, 10));

Map<Integer, List<Integer>> map =list.stream()
    .collect(Collectors.groupingBy(
         Collection::size,
         Collectors.flatMapping(
             l -> l.stream().filter(i -> i % 2 == 0),
             Collectors.toList())));

{4=[8, 10], 6=[2, 4, 6]}

This is a fairly elegant way using just 3 collectors. I need to rewrite the collector in where is not yet supported. My attempt use 6 Collectors that is quite an extensive usage and I am not able to figure out a way using less of them:

Map<Integer, List<Integer>> map = list.stream()
    .collect(Collectors.groupingBy(
        Collection::size,
        Collectors.collectingAndThen(
            Collectors.mapping(
                l -> l.stream().filter(i -> i % 2 == 0).collect(Collectors.toList()),
                Collectors.toList()),
            i -> i.stream().flatMap(j -> j.stream()).collect(Collectors.toList()))));

Is there a shorter better way using solely ?

Best Answer

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