Java 8 Collector for Gauva’s LinkedHashMultimap

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.

Leave a Reply