15

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 ?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • 4
    @Michael agreed, you should post a version of this back-port actually, would be glad to upvote. I thought there would be already one by Holger, but I can't find it on stack overflow... – Eugene Jan 21 '19 at 10:13
  • 5
    [found it...](https://stackoverflow.com/questions/39130122/java-8-nested-multi-level-group-by/39131049#39131049) – Eugene Jan 21 '19 at 10:23

2 Answers2

19

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()
        )
    )
);
Michael
  • 41,989
  • 11
  • 82
  • 128
  • 5
    you deserve two upvotes for `Nikollectors`... you should add an example of the usage too and may be a static factory method that would return this `Collectors::flatMapping` to make it a full example IMHO – Eugene Jan 21 '19 at 10:17
  • 6
    you might be joking, but in my project we have a package `xxx.HolgerUtil` - just to be sure where this was taken from, no joke. :) – Eugene Jan 21 '19 at 10:21
  • I'm stuck in java 8 and this is the answer I didn't realize I was looking for. Thanks! – moraleboost Oct 06 '20 at 17:16
11

For just this particular case, I guess this would be a simpler version:

Map<Integer, List<Integer>> map =
        list.stream()
            .collect(Collectors.toMap(
                Collection::size,
                x -> x.stream().filter(y -> y % 2 == 0).collect(Collectors.toList())
            ));

If there would be merging involved (two collections that would have the same size), I would add a merge function that is pretty trivial:

 Map<Integer, List<Integer>> map =
        list.stream()
            .collect(Collectors.toMap(
                Collection::size,
                x -> x.stream().filter(y -> y % 2 == 0).collect(Collectors.toCollection(ArrayList::new)),
                (left, right) -> {
                    left.addAll(right);
                    return left;
                }
            ));

Otherwise, I agree with Michael in this comment, this is not that hard to back-port to java-8.

Eugene
  • 117,005
  • 15
  • 201
  • 306