2
\$\begingroup\$

I wrote a collector that would take a stream of Map.Entry<Integer, MyObject> which I want to make into Map<Integer, Set<MyObject>>

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;
class MyObject {

}
class Main {
  public static void main(String[] args) {
    Map<Integer, Set<MyObject>> collected = Stream.of(
       Map.entry(123, new MyObject()),
       Map.entry(123, new MyObject()),
       Map.entry(234, new MyObject()),
       Map.entry(345, new MyObject())
    )
    .collect(
      ConcurrentHashMap<Integer, Set<MyObject>>::new,
      (map, entry) -> {
        map.computeIfAbsent(
          entry.getKey(), 
          k -> new HashSet<MyObject>()
        )
        .add(entry.getValue());
      },
      (map1, map2) -> {
        map2.forEach((map2Key, map2Object) -> {
          final Set<MyObject> objects = 
            map1.computeIfAbsent(map2Key, k -> new HashSet<MyObject>());
          objects.addAll(map2Object);
        });
      }
    );
    System.out.println(collected);
  }
}

I was wondering if there's a built in way of doing this, as it seems to be a common thing to do.

One way I can refactor this is to move the collector out to a different class like so:

class EntryToMapCollector<K, V> implements Collector<Map.Entry<K, V>, Map<K, Collection<V>>, Map<K, Collection<V>>> {
    @Override
    public Supplier<Map<K, Collection<V>>> supplier() {
        return ConcurrentHashMap<K, Collection<V>>::new;
    }

    @Override
    public BiConsumer<Map<K, Collection<V>>, Map.Entry<K, V>> accumulator() {
        return (map, entry) -> {
            map.computeIfAbsent(entry.getKey(), k -> new HashSet<V>())
                .add(entry.getValue());
        };
    }

    @Override
    public BinaryOperator<Map<K, Collection<V>>> combiner() {
        return (map1, map2) -> {
            final Map<K, Collection<V>> combined = new ConcurrentHashMap<>(map1);
            map2.forEach((map2Key, map2Observations) -> {
                combined.computeIfAbsent(map2Key, k -> new HashSet<V>())
                    .addAll(map2Observations);
            });
            return combined;
        };
    }

    @Override
    public Function<Map<K, Collection<V>>, Map<K, Collection<V>>> finisher() {
        return m -> m;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Set.of(Characteristics.CONCURRENT, Characteristics.UNORDERED);
    }
}

and replace the call with .collect(new EntryToMapCollector<Integer, MyObject>());

https://repl.it/@trajano/ForkedSpryRobot

\$\endgroup\$
3

1 Answer 1

4
\$\begingroup\$

Have you looked at .groupingBy() and .toSet()?

Map<Integer, Set<MyObject>> collected = Stream.of(...)
    .collect(Collectors.groupingBy(Map.Entry::getKey,
                                   Collectors.mapping(Map.Entry::getValue, Collectors.toSet())));

You possibly want .groupingByConcurrent() instead, though it is not exactly clear why.

Not a repl.it, but a JShell log:

U:\>jshell
|  Welcome to JShell -- Version 13
|  For an introduction type: /help intro

jshell> class MyObject { }
|  created class MyObject

jshell> Map<Integer, Set<MyObject>> result = Stream.of(
   ...>    Map.entry(123, new MyObject()),
   ...>    Map.entry(123, new MyObject()),
   ...>    Map.entry(234, new MyObject())
   ...> ).collect(
   ...>    Collectors.groupingByConcurrent(
   ...>       Map.Entry::getKey,
   ...>       Collectors.mapping(
   ...>          Map.Entry::getValue,
   ...>          Collectors.toSet()
   ...>       )
   ...>    )
   ...> );
result ==> {234=[MyObject@78c03f1f], 123=[MyObject@5ce81285, MyObject@52a86356]}

jshell>
\$\endgroup\$
3
  • \$\begingroup\$ what's mapping? \$\endgroup\$ Dec 20, 2019 at 22:11
  • \$\begingroup\$ Collectors.mapping() \$\endgroup\$
    – AJNeufeld
    Dec 20, 2019 at 22:12
  • \$\begingroup\$ Do you mind posting a repl.it for your solution? I tried to put it in and I got Main.java:17: error: incompatible types: inference variable D has incompatible equality constraints Collection<MyObject>,Set<T#2> \$\endgroup\$ Dec 20, 2019 at 22:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.