Hot questions for Operating MultiMap in Guava

Top 10 Java Open Source / Guava / Operating MultiMap

Map implementation with duplicate keys

Question: I want to have a map with duplicate keys.

I know there are many map implementations (Eclipse shows me about 50), so I bet there must be one that allows this. I know it's easy to write your own map that does this, but I would rather use some existing solution.

Maybe something in commons-collections or google-collections?

Answer: You are searching for a multimap, and indeed both commons-collections and Guava have several implementations for that. Multimaps allow for multiple keys by maintaining a collection of values per key, i.e. you can put a single object into the map, but you retrieve a collection.

I would prefer Guava's Multimap as it is generics-aware.

For example:

Multimap<Integer, String> multimap = ArrayListMultimap.create();

multimap.put(1, "A");
multimap.put(1, "B");
multimap.put(1, "C");
multimap.put(1, "A");

multimap.put(2, "A");
multimap.put(2, "B");
multimap.put(2, "C");

multimap.put(3, "A");

System.out.println(multimap.get(1));
System.out.println(multimap.get(2));       
System.out.println(multimap.get(3));

Output is:

[A,B,C,A]
[A,B,C]
[A]

How to create a Multimap<K,V> from a Map<K, Collection<V>>?

Question: I didn't find such a multimap construction... When I want to do this, I iterate over the map, and populate the multimap. Is there an other way?

final Map<String, Collection<String>> map = ImmutableMap.<String, Collection<String>>of(
            "1", Arrays.asList("a", "b", "c", "c"));
System.out.println(Multimaps.forMap(map));

final Multimap<String, String> expected = ArrayListMultimap.create();
for (Map.Entry<String, Collection<String>> entry : map.entrySet()) {
    expected.putAll(entry.getKey(), entry.getValue());
}
System.out.println(expected);

The first result is {1=[[a, b, c, c]]} but I expect {1=[a, b, c, c]}

Answer: Assuming you have

Map<String, Collection<String>> map = ...;
Multimap<String, String> multimap = ArrayListMultimap.create();

Then I believe this is the best you can do

for (String key : map.keySet()) {
  multimap.putAll(key, map.get(key));
}

or the more optimal, but harder to read

for (Entry<String, Collection<String>> entry : map.entrySet()) {
  multimap.putAll(entry.getKey(), entry.getValue());
}

Cleanest way to create a guava MultiMap from a java8 stream

Question: 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?

Answer: You can just use the Guava Multimaps factory:

Multimaps.index(foos, Foo::getId);

or wrap a call to Multimaps.index with a Collector 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);
    }
}

Adding a key with an empty value to Guava Multimap

Question: I have a need to add a key to a Guava Multimap with an empty collection as the value. How do I accomplish this?

I tried this:

map.put( "my key", null );

but calling get() returns a list with one element, which is null. I worked around this by doing the following:

map.putAll("my key2", new ArrayList())

but I'm wondering if this is a bad thing to do? I know Guava automatically removes a key when the last value is removed to keep containsKey() consistent. What's my best option here?

Answer: Multimap deliberately forbids this approach, and your proposed workaround is a no-op -- it won't actually do anything.

The way Multimap works is that multimap.get(key) never returns null, but always returns some collection -- possibly empty. (But the backing Multimap implementation probably doesn't actually store anything for that key, and if a key isn't mapped to a nonempty collection, it won't e.g. appear in the keySet(). Multimap is not a Map<K, Collection<V>>.)

If you want to map to an empty collection, you must use Map<K, List<V>>.


Collect Lines using Multimap Collector

Question: Is there a way to covert the below to using collectors yet?

List<String[]> lines = getLines();

Multimap<String,String> multimap = ArrayListMultimap.create();

lines.forEach(line -> 
multimap.put(line[0],line[1]);
);

Answer: You can use Multimaps.toMultimap collector:

ListMultimap<String, String> multimap = lines.stream()
        .collect(Multimaps.toMultimap(
                l -> l[0],
                l -> l[1],
                ArrayListMultimap::create
        ));

Or if you don't need mutability, use ImmutableListMultimap.toImmutableListMultimap collector:

ListMultimap<String, String> multimap = lines.stream()
        .collect(toImmutableListMultimap(l -> l[0], l -> l[1]));

Retrieving specific values in Multimap

Question: I'm using a Multimap that has two values per key. Below is the code I'm using to get each value separately:

The first bit of code gets the first object value:

for(Object object : map.get(object))
{
    return object
}

Then, I'm using another method to retrieve the other value. This method takes the first object as an argument:

for(Object object : team.get(object))
{
    if(object != initialObject)
    {
        return object;
    }
}

This seems like a 'hackish' way of doing things, so is there any way for me to get the values more easily?

Answer: If you're using Guava, Iterables#get is probably what you want - it returns the Nth item from any iterable, example:

Multimap<String, String> myMultimap = ArrayListMultimap.create();

// and to return value from position:
return Iterables.get(myMultimap.get(key), position);

If you're using a ListMultimap then it behaves very much like a map to a List so you can directly call get(n).


Java Guava combination of Multimap and Cache

Question: Is there any such thing as a combination of Guava's Cache and Multimap functionality available? Essentially, I need a collection where entries expire after a given time such as available in Cache but I have non-unique keys and I need the entries to expire independently.

Answer: I think that Louis Wasserman provided the answer in one of the comments above, i.e. that there is no off-the-shelf combo of Multimap and Cache available. I have solved my problem/requirements with the solution outlined in pseudo-code below:

private Cache<Integer,Object> cache = CacheBuilder.newBuilder().SomeConfig.build();
private Multimap<Integer,Object> multimap = HashMultimap<Integer, Object>.create();
private AtomicInteger atomicid = new AtomicInteger(0);

public void putInMultimap(int id, Object obj) {
   int mapid = atomicid.addAndGet(1);
   cache.put(mapid,obj);
   multimap.put(id,mapid);
}
public List<Object> getFromMultimap(int id) {
   Set<Integer> mapids = multimap.get(id);
   List<Object> list = new ArrayList<Object>();
   for (int i : mapids) {
      list.add(cache.getIfPresent(i));
   }
   return list;
}

This simple 'solution' has some limitations but it works OK for me.


Is there a way to get all keys from a value in a multimap?

Question: Say I have a guava Multimap. I have a value, "Foo", that may belong to one or more keys. Is there any way I can figure out which keys contain an entry "Foo"?

Answer: You can invert the Multimap. For this you can use the method Multimaps.invertFrom.

For example, if your Multimap is a Multimap<String, String>

Multimap<String, String> invertedMultimap = Multimaps.invertFrom(myMultimap, ArrayListMultimap.<String, String>create());