10 months ago or so I wrote a blog post entitled Immutable List Collector in Java 8 (that probably should have been named “Unmodifiable List Collector in Java 8” to be more precise) that presented a way to turn a Stream into an unmodifiable list. I was recently asked if I could present a Collector for the LinkedHashMultimap in Guava which is what I will do here.
The LinkedHashMultimapCollector
In it’s simplest form it can look like this:
import com.google.common.collect.LinkedHashMultimap;
import java.util.stream.Collector;
import static java.util.Map.Entry;
public class LinkedHashMultimapCollector {
public static Collector, LinkedHashMultimap, LinkedHashMultimap> toLinkedHashMultimap() {
return Collector.of(LinkedHashMultimap::create, (acc, entry) -> acc.put(entry.getKey(), entry.getValue()), (map1, map2) -> {
map1.putAll(map2);
return map1;
});
}
}
The implementation is quite similar to what I did in the previous blog so read up on the details there. This allows us to turn a Streams of entries (key, value) into a LinkedHashMultimap. For example:
import com.google.common.collect.LinkedHashMultimap;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class LinkedHashMultimapCollectorTest {
@Test public void
example_of_linked_hash_multimap_collector() {
// Given
Map testing = new HashMap() {{ put("one", 1); put("two", 2); put("three", 3);}};
// When
LinkedHashMultimap multimap = testing.entrySet().stream().filter(e -> e.getValue() <= 2).collect(LinkedHashMultimapCollector.toLinkedHashMultimap());
// Then
assertThat(multimap.asMap()).containsKeys("one", "two");
}
}
Making it more generic
While this works for LinkedHashMultimap
it would be nice to make the collector work for other instances of Multimap
. So let's modify the collector to do so:
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import java.util.function.Supplier;
import java.util.stream.Collector;
import static java.util.Map.Entry;
public class MultimapCollector {
public static > Collector, A, A> toMultimap(Supplier supplier) {
return Collector.of(supplier, (acc, entry) -> acc.put(entry.getKey(), entry.getValue()), (map1, map2) -> {
map1.putAll(map2);
return map1;
});
}
}
Now the toMultimap
method accepts a Supplier that creates a user defined implementation of a Multimap
. As an example consider if we want to return a ArrayListMultimap
instead of a LinkedHashMultimap
in the test case from the previous example. With the more generic collector in place we could now do like this:
ArrayListMultimap multimap = testing.entrySet().stream().filter(e -> e.getValue() <= 2).collect(toMultimap(ArrayListMultimap::create));
To use the toMultimap
method to create a LinkedHashMultimap
we simply create a supplier for this type instead:
LinkedHashMultimap multimap = testing.entrySet().stream().filter(e -> e.getValue() <= 2).collect(toMultimap(LinkedHashMultimap::create));
Of course we could easily extend the MultimapCollector
class with a new method that explicitly return us a LinkedHashMultimap
:
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import java.util.function.Supplier;
import java.util.stream.Collector;
import static java.util.Map.Entry;
public class MultimapCollector {
public static > Collector, A, A> toMultimap(Supplier supplier) {
return Collector.of(supplier, (acc, entry) -> acc.put(entry.getKey(), entry.getValue()), (map1, map2) -> {
map1.putAll(map2);
return map1;
});
}
public static Collector, LinkedHashMultimap, LinkedHashMultimap> toLinkedHashMultimap() {
return toMultimap(LinkedHashMultimap::create);
}
}
Conclusion
Once you see a Collector implementation like the ones in this blog they don't look all that complex in the end. But in my experience it can be hard to get the types and generics right and even a seemingly quite straight-forward collector can take a while to get right.