Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cleanest way to create a Guava Multimap from a Java 8 stream

I have a List<Foo> and want a Multimap<String, Foo> where we've grouped the Foo's by their getId() function.

I am using Java 8 and its almost awesome in that you can do:

List<Foo> foos = ... Map<String, List<Foo>> foosById = foos.stream().collect(groupingBy(Foo::getId)); 

However, I have a good amount of code that wants a MultiMap<String, Foo> so this doesnt save me anything and I'm back to using a for-loop to create my Multimap. Is there a nice "functional" way that I am missing?

like image 950
Scott B Avatar asked Apr 11 '14 04:04

Scott B


People also ask

How do you create a Multimap in Java?

Following is a simple custom implementation of the Multimap class in Java using a Map and a Collection . * Add the specified value with the specified key in this multimap. * Returns the Collection of values to which the specified key is mapped, * or null if this multimap contains no mapping for the key.

Is Guava Multimap thread safe?

Returns a synchronized (thread-safe) SortedSetMultimap backed by the specified multimap.

What is guava Multimap?

A Multimap is a new collection type that is found in Google's Guava library for Java. A Multimap can store more than one value against a key. Both the keys and the values are stored in a collection, and considered to be alternates for Map<K, List<V>> or Map<K, Set<V>> (standard JDK Collections Framework).

What is ArrayListMultimap?

The ArrayListMultimap is a variation of a Map in which multiple values or objects are associated with a single key but it allows duplicate key/value pairs in the Map.


2 Answers

You can just use the Guava Multimaps factory:

ImmutableMultimap<String, Foo> foosById = Multimaps.index(foos, Foo::getId); 

or wrap a call to Multimaps.index with a Collector<T, A, R> interface (shown below, in an unoptimized naive implementation).

Multimap<String, Foo> collect = foos.stream()         .collect(MultimapCollector.toMultimap(Foo::getId)); 

and the Collector:

public class MultimapCollector<T, K, V> implements Collector<T, Multimap<K, V>, Multimap<K, V>> {      private final Function<T, K> keyGetter;     private final Function<T, V> valueGetter;      public MultimapCollector(Function<T, K> keyGetter, Function<T, V> valueGetter) {         this.keyGetter = keyGetter;         this.valueGetter = valueGetter;     }      public static <T, K, V> MultimapCollector<T, K, V> toMultimap(Function<T, K> keyGetter, Function<T, V> valueGetter) {         return new MultimapCollector<>(keyGetter, valueGetter);     }      public static <T, K, V> MultimapCollector<T, K, T> toMultimap(Function<T, K> keyGetter) {         return new MultimapCollector<>(keyGetter, v -> v);     }      @Override     public Supplier<Multimap<K, V>> supplier() {         return ArrayListMultimap::create;     }      @Override     public BiConsumer<Multimap<K, V>, T> accumulator() {         return (map, element) -> map.put(keyGetter.apply(element), valueGetter.apply(element));     }      @Override     public BinaryOperator<Multimap<K, V>> combiner() {         return (map1, map2) -> {             map1.putAll(map2);             return map1;         };     }      @Override     public Function<Multimap<K, V>, Multimap<K, V>> finisher() {         return map -> map;     }      @Override     public Set<Characteristics> characteristics() {         return ImmutableSet.of(Characteristics.IDENTITY_FINISH);     } } 
like image 186
Burg Avatar answered Oct 05 '22 23:10

Burg


Guava 21.0 introduced several methods that return Collector instances which will convert a Stream into a Multimap grouped by the result of applying a function to its elements. These methods are:

  • ImmutableListMultimap.toImmutableListMultimap — creates an ImmutableListMultimap
  • ImmutableSetMultimap.toImmutableSetMultimap — creates an ImmutableSetMultimap
  • Multimaps.toMultimap — Creates a Multimap using the given Supplier
ImmutableListMultimap<String, Foo> foosById = foos.stream().collect(         ImmutableListMultimap.toImmutableListMultimap(                 Foo::getId, Function.identity())); 
ImmutableSetMultimap<String, Foo> foosById = foos.stream().collect(         ImmutableSetMultimap.toImmutableSetMultimap(                 Foo::getId, Function.identity())); 
HashMultimap<String, Foo> foosById = foos.stream().collect(         Multimaps.toMultimap(                 Foo::getId, Function.identity(), HashMultimap::create) ); 
like image 20
M. Justin Avatar answered Oct 05 '22 23:10

M. Justin