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

JavaJava StreamGuava

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionScott BView Question on Stackoverflow
Solution 1 - JavaBurgView Answer on Stackoverflow
Solution 2 - JavaM. JustinView Answer on Stackoverflow