Guava's Multimap


Guava's Multimap interface and its implementations give Java developers a new tool. They replace Map<X, List<Y>>, as well as Map<X, Set<Y>>, both of which can be a little awkward and verbose.

Multimap Summary

For those in a hurry, here's a quick summary of how to use a Multimap.

Listing 1: Snapshot on how to use a Multimap

// Initializing
Multimap<String, String> mm = HashMultimap.create();

// Insert in a single step.
mm.putAll("be", Sets.newHashSet("am", "are", "is", "was", "were"));

// Add items individually.
mm.put("ask", "ask");
mm.put("ask", "asks");
mm.put("ask", "asked");

// get( )
mm.get("be");
mm.get("ask");

// iterate through all the entries
for(Entry<String, String> mapPair : mm.entries())  {
    String key = mapPair.getKey();
    String value = mapPair.getValue();
}

Advantages of Multimap. Here are some of the advantages a multimap has over, for example, Map<X, List<Y>>.

  • You don't have to test whether the key exists in the map before inserting new values.
  • You can query on a non-existent map key without worrying about a null pointer exception.
  • You can iterate through all values without first obtaining the list associated with each key.
  • With one method call, in O(1) time, you can get the number of all entries in the map.

That's the summary. The next sections explore Multimaps in the detail they deserve, expanding on the examples given above.


Multimap Functionality

Creating and populating. For the sake of illustration, suppose we want to associate a number of simple forms of an English verb with their infinitive form. Without Multimap, you would probably use a Map<String, Set<String>>, where the key was the infinitive form, and the set associated with each key consisted of all the various forms for that verb.

With Guava, you would use a Multimap<String, String> instead. The lines below show how to create the multimap and load it with the forms for two verbs, ask and be.

Listing 2: Creating and populating a Multimap

// Instantiate the map.
Multimap<String, String> mm = HashMultimap.create();

// Insert items individually.
mm.put("ask", "ask");
mm.put("ask", "asks");
mm.put("ask", "asked");

// Insert in a two-step process.
Set<String> beList = Sets.newHashSet("am", "are", "is", "was", "were");
mm.putAll("be", beList);

// Insert in a single step.
mm.putAll("have", Sets.newHashSet("have", "has", "had"));

// Or allow duplicate values with: Multimap<String, String> mm2 = ArrayListMultimap.create();

Listing 2 shows that you can load each list element individually, with put( ), or collectively, with putAll( ). Significantly, in neither case do you have to worry about whether the map already contains some verb forms that you don't want to lose.

Using Multimap with arrays instead of sets. Note the commented-out line at the end, which shows how to instantiate a Multimap that allows repeated values that behave like an ArrayList, rather than values that have HashSet-like behavior.

Multimap get( ). Now suppose you've populated your map and you want to do something useful with it.

Listing 3: Getting particular Multimap values

// Getting particular entries.
Set<String> expected = Sets.newHashSet("am", "are", "is", "was", "were");
assertEquals(expected, mm.get("be"));
assertEquals(0, mm.get("do").size());

Listing 3 uses unit tests to show how to get the Multimap values for a given key. First we verify that calling get("be") on the multimap we created in the previous listing returns the expected forms of the verb "be." The second assertEquals( ) statement sees how many entries the multimap contains for the key "do." In this case, there are none. Here we see a significant advantage that multimap has over Map<X, List<Y>>, where get("do") would return null, and calling size( ) on that would give you nothing but a lousy NullPointerException.

Iterating. Let's move on now to iteration.

Listing 4: Iterating through multimap entries

// Iterate through all the entries
for(Entry<String, String> mapPair : mm.entries())  {
    String key = mapPair.getKey();
    String value = mapPair.getValue();
    System.out.println("key = " + key + "; value = " + value);
}

Output:
key = ask; value = ask
key = ask; value = asks
key = ask; value = asked
key = be; value = am
key = be; value = are
key = be; value = is
key = be; value = was
key = be; value = were

The above code snippet shows that to iterate through a multimap, you don't have to retrieve every key separately and then iterate through all the elements of its list. The entries( ) method does this work for you.

The useful toString( ) override. The next listing shows the multimap's toString( ) override, which allows you to see all the keys and their values without doing any iteration yourself.

Listing 5: Multimap's toString( )

System.out.println("Write using toString():");
System.out.println(mm.toString());

Output:
Write using toString():
{ask=[ask, asks, asked], be=[am, are, is, was, were]}

Getting just the values, without the keys. Perhaps you're completely indifferent about the keys, and all you want is the elements of all the lists. The next example gets all the elements with a method call to values( ); then, just for the fun of it, the code snippet goes on to sort those entries and write out the result.

Listing 6: Getting just the entries with Multimap.values( )

// Getting all entries when you don't care about the key.
Collection<String> valuesAsCollection = mm.values();

// Sort the values.
List<String> valuesAsList = Lists.newArrayList(valuesAsCollection);
Collections.sort(valuesAsList);
System.out.println(valuesAsList);

Output:
[am, are, ask, asked, asks, is, was, were]

Getting the count of all entries. Suppose you want a count of all the entries in your multimap. The snippet below shows you, first, how to do this with Multimap; then it shows you how you would have to do this if you used the traditional Map<X, Set<Y>>.

Listing 7: Getting a count of all the entries in the Multimap—with and without Guava

    // With Guava
    size = mm2.size();
    assertEquals(5, size);
    
    // Without Guava, using a Map<X, Set<Y>>, requires a loop
    int size = 0;
    for(String key : mm1.keySet()) {
        size = size + mm1.get(key).size();
    }
    assertEquals(5, size);

Other map-like methods. In addition to size( ), which is shown above, the Multimap interface offers you other methods you expect from an ordinary map. The next listing demonstrates some of these.

Listing 8: Other useful Multimap methods

// Testing the values for the key "be".
// containsKey( )
assertTrue(mm.containsKey("be"));

// get( )
Collection<String> beForms = mm.get("be");
assertEquals(5, beForms.size());

// containsEntry( )
assertTrue(mm.containsEntry("be", "was"));

// keySet( )
Set<String> keys = mm.keySet();
assertEquals(2, keys.size());

Multimap to Multiset. Finally, the keys( ) method returns another Guava interface called Multiset. The next listing gives you just a taste of what a Multiset can do for you. See the link at right for more.

Listing 9: Getting a Multiset from Multimap.keys( )

Multiset<String> multiset = mm.keys();
int size = multiset.size();
assertEquals(8, size);
assertEquals(5, multiset.count("be"));
assertEquals(3, multiset.count("ask"));