Google Guava’s Multimap class in Java
Google Guava’s Multimap
interface allows mapping a single key to multiple values in Java, unlike java.util.Map
where a key can only be mapped to a single value. This post will discuss how to use Guava’s MultiMap
in Java and discuss its advantages over standard map implementation.
A Multimap is a collection that maps keys to values, similar to a map, but each key may be associated with multiple values. In the previous post, we have seen how to implement our own Multimap
class in Java using a map and a Collection
. Multimap
implementation is also provided by many third-party libraries such as Guava and Apache Commons. Let’s discuss various utility methods provided by Guava’s Multimap
interface:
1. Basic Usage: Initializer, put()
, get()
methods
We know that if we put two values into a java.util.Map
using the same key, the second put operation will override the value set by the first put()
operation.
1 2 3 4 5 6 |
java.util.Map<String, String> map = new java.util.HashMap(); map.put("John", "Tyler"); map.put("John", "Kennedy"); System.out.println(map); // {John=Kennedy} |
The above behavior enforces the map’s key to associate with only one value, which is sometimes undesirable. On the other hand, Guava’s MultiMap
allows adding one or more values for the same key by maintaining a Collection of values. Any subsequent put()
operations on a key will not override the values set by the previous put()
operations on that key.
1 2 3 4 5 6 7 |
ListMultimap<String, String> multimap = ArrayListMultimap.create(); multimap.put("John", "Adams"); multimap.put("John", "Tyler"); multimap.put("John", "Kennedy"); System.out.println(multimap); // {John=[Adams, Tyler, Kennedy]} |
Please note that few multimap implementations like ArrayListMultimap
allow duplicate key-value pairs, and some implementations like ForwardingSetMultimap
doesn’t allow duplicates in it. This basically depends on the underlying data structure. For example, a List
supports duplicates, whereas a set doesn’t. Instead of using the Multimap
interface directly, one should prefer using the subinterfaces ListMultimap
and SetMultimap
.
The get()
method of Guava’s MultiMap
returns a view of collection of the values associated with the specified key in the multimap. It returns an empty collection when the key is not present on the map. Since a view is returned, the underlying multimap gets updated on doing any changes to the returned collection, and vice versa.
1 2 |
Collection<String> values = multimap.get("John"); System.out.println(values); // [Adams, Tyler, Kennedy] |
Following is a simple Java program to demonstrate Guava’s MultiMap
interface in Java. It uses ArrayListMultimap
implementation of ListMultimap
that internally uses an ArrayList
to store the values for a given key.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import java.util.List; class Main { // Demonstrate Guava's `MultiMap` class in Java public static void main(String[] args) { ListMultimap<String, String> multimap = ArrayListMultimap.create(); multimap.put("John", "Adams"); multimap.put("John", "Tyler"); multimap.put("John", "Kennedy"); multimap.put("George", "Washington"); multimap.put("George", "Bush"); System.out.println("John" + ": " + multimap.get("John")); System.out.println("George" + ": " + multimap.get("George")); } } |
Output:
John: [Adams, Tyler, Kennedy]
George: [Washington, Bush]
2. Converting Multimap<K,V>
to Map<K,Collection<V>>
Guava’s MultiMap
maintains a flattened collection of key-value pairs like <K,V>
. We can convert it to a map where each distinct key K is mapped to a non-empty collection of that key’s associated values like <K,Collection<V>>
using asMap()
method. Please note that the returned map Map<K,Collection<V>>
is backed by the underlying multimap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import java.util.Collection; import java.util.Map; class Main { // Demonstrate `asMap()` operation in Guava's `MultiMap` public static void main(String[] args) { ListMultimap<String, String> multimap = ArrayListMultimap.create(); multimap.put("John", "Adams"); multimap.put("John", "Tyler"); multimap.put("George", "Washington"); multimap.put("George", "Bush"); Map<String, Collection<String>> map = multimap.asMap(); map.get("John").add("Kennedy"); System.out.println("John" + ": " + map.get("John")); // since the underlying multimap backs the returned map, // multimap also gets updated System.out.println("John" + ": " + multimap.get("John")); } } |
Output:
John: [Adams, Tyler, Kennedy]
John: [Adams, Tyler, Kennedy]
3. Iterate over the Multimap
Guava’s MultiMap
provides keySet(), entries(), values(), keys()
methods which are similar to the corresponding view collections of Map.
Input multimap (for below methods):
George –> Washington, Bush
Zachary –> Taylor
John –> Adams, Tyler, Kennedy
Grover –> Cleveland
⮚ Using keySet()
method
1 2 3 4 5 6 7 |
// Iterate over Guava's `MultiMap` using `keySet()` method public static<K, V> void iterate(ListMultimap<K, V> multimap) { for (K key: multimap.keySet()) { System.out.println(key + ": " + multimap.get(key)); } } |
Output:
George: [Washington, Bush]
Zachary: [Taylor]
John: [Adams, Tyler, Kennedy]
Grover: [Cleveland]
⮚ Using entries()
method
1 2 3 4 5 6 7 |
// Iterate over Guava's `MultiMap` using `entries()` method public static<K, V> void iterate(ListMultimap<K, V> multimap) { for (Map.Entry<K, V> entry: multimap.entries()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } } |
Output:
George: Washington
George: Bush
Zachary: Taylor
John: Adams
John: Tyler
John: Kennedy
Grover: Cleveland
⮚ Using keys()
and values()
method
1 2 3 4 5 6 7 8 9 10 |
// Iterate over Guava's `MultiMap` keys using `keys()` method and // values using `values()` method public static<K, V> void iterate(ListMultimap<K, V> multimap) { Multiset<K> multiset = multimap.keys(); System.out.println(multiset); Collection<V> values = multimap.values(); System.out.println(values); } |
Output:
[George x 2, Zachary, John x 3, Grover]
[Washington, Bush, Taylor, Adams, Tyler, Kennedy, Cleveland]
4. Remove/Replace existing keys/values in the Multimap
Guava’s MultiMap
provides the remove()
method that removes a single key-value pair from the multimap that matches the specified key-value pair. It returns true if the pair is removed; otherwise, it returns false if no such pair is found.
Guava’s MultiMap
also has the removeAll()
method that removes all values associated with the specified key and returns the collection of removed values.
For replacing the existing values for a particular key, Guava provides the replaceValues()
method. If no values were previously associated with the key, replaceValues()
will create the mapping between the specified key and value. It returns the collection of replaced values or an empty collection if no values are replaced.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; import java.util.Arrays; class Main { // Demonstrate `remove()`, `removeAll()`, and `replaceValues()` methods // of Guava's `MultiMap` public static void main(String[] args) { ListMultimap<String, String> multimap = ArrayListMultimap.create(); multimap.put("Zachary", "Taylor"); multimap.put("John", "Adams"); multimap.put("John", "Tyler"); multimap.put("John", "Kennedy"); multimap.put("George", "Washington"); multimap.put("George", "Bush"); System.out.println("------------Before------------\n"); System.out.println(multimap); // Remove all values associated with the "George" key multimap.removeAll("George"); // Remove the "Tyler" value associated with the "George" key multimap.remove("John", "Tyler"); // Replace existing values of the "Zachar" key with the specified collection multimap.replaceValues("Zachar", Arrays.asList("T.")); // since "Obama" is not present in the multimap, `replaceValues()` // will create the mapping multimap.replaceValues("Obama", Arrays.asList("Barak")); System.out.println("\n------------After------------\n"); System.out.println(multimap); } } |
Output:
– – – – – – – – – – – – Before – – – – – – – – – – –
{George=[Washington, Bush], Zachary=[Taylor], John=[Adams, Tyler, Kennedy]}
– – – – – – – – – – – – After – – – – – – – – – – –
{Zachar=[T.], Obama=[Barak], Zachary=[Taylor], John=[Adams, Kennedy]}
5. Using Size()
of the multimap
We can use the size()
method to determine the total number of key-value pairs in the multimap. Please note that this method does not return the total number of distinct keys in the multimap. To get the total number of distinct keys, consider using keySet().size()
or asMap().size()
.
6. Finding key/values in the multimap
Guava provides three methods, namely containsKey()
, containsValue()
and containsEntry()
to check if the multimap contains at least one key-value pair with the specified key, specified value, and the specified key-value pair, respectively.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; class Main { // Demonstrate `containsKey()`, `containsValue()`, and `containsEntry()` // method of Guava's `MultiMap` public static void main(String[] args) { ListMultimap<String, String> multimap = ArrayListMultimap.create(); multimap.put("John", "Tyler"); multimap.put("John", "Kennedy"); multimap.put("George", "Washington"); multimap.put("George", "Bush"); // check if multimap contains at least one key-value pair // with the "John" as the key if (multimap.containsKey("John")) { System.out.println("Multimap contains the \"John\" key"); } // check if multimap contains at least one key-value pair // with the "Kennedy" as the value if (multimap.containsValue("Kennedy")) { System.out.println("Multimap contains the \"Kennedy\" value"); } // check if multimap contains at least one key-value pair // with "George" as the key and "Washington" as the value if (multimap.containsEntry("George", "Washington")) { System.out.println("Multimap contains the specified mapping"); } } } |
Output:
Multimap contains the “John” key
Multimap contains the “Kennedy” value
Multimap contains the specified mapping
7. Immutable Multimap
in Guava
Guava’s Multimap
interface has three immutable implementations – ImmutableMultimap
, ImmutableListMultimap
, and ImmutableSetMultimap
, which should always be preferred over the mutable implementations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ListMultimap; class Main { // Immutable `Multimap` in Guava public static void main(String[] args) { ListMultimap<String, String> immutableMultimap = ImmutableListMultimap.<String, String>builder() .put("Zachary", "Taylor") .put("John", "Adams") .put("John", "Tyler") .put("John", "Kennedy") .put("George", "Washington") .put("George", "Bush") .put("Grover", "Cleveland").build(); System.out.println("John" + ": " + immutableMultimap.get("John")); try { // this will fail since the map is immutable immutableMultimap.put("Obama", "Barack"); } catch (UnsupportedOperationException ex) { System.out.print("java.lang.UnsupportedOperationException thrown"); } } } |
Output:
John: [Adams, Tyler, Kennedy]
java.lang.UnsupportedOperationException thrown
Pros of Guava’s Multimap
over java.util.Map
Guava’s Multimap
is commonly used in places where a Map<K, Collection<V>> would otherwise have appeared. The differences include:
- In Guava’s
Multimap
, there is no need to populate an empty collection before adding an entry withput()
. - The
get()
method of Guava’sMultimap
returns an empty collection whencontainsKey(key)
is false, unlikeget()
method ofjava.util.Map
, which returns anull
. - Unlike
java.util.Map
, a key is contained in Guava’sMultimap
if and only if it maps to at least one value. Any operation that causes a key to has zero associated values has the effect of removing that key from the Multimap. - The
size()
method of Guava’sMultimap
returns the exact number of key-value pairs contained in it. Thesize()
method ofjava.util.Map
, on the other hand, returns the total number of distinct keys present in it.
That’s all about Guava’s Multimap
class in Java.
Reference: Multimap (Guava: Google Core Libraries for Java 23.0 API)
Thanks for reading.
To share your code in the comments, please use our online compiler that supports C, C++, Java, Python, JavaScript, C#, PHP, and many more popular programming languages.
Like us? Refer us to your friends and support our growth. Happy coding :)