Guava Set + Function = Map – Guava集+函数=地图

最后修改: 2016年 6月 17日

1. Overview


In this tutorial – we will illustrate one of many useful features in Guava‘s collect package: how to apply a Function to a Guava Set and obtain a Map.


We’ll discuss two approaches – creating an immutable map and a live map based on the built-in guava operations and then an implementation of a custom live Map implementation.


2. Setup


First, we’ll add the Guava library as a dependency in pom.xml:



A quick note – you can check if there’s a newer version here.


3. The Mapping Function


Let’s first define the function that we’ll apply on the sets elements:


    Function<Integer, String> function = new Function<Integer, String>() {
        public String apply(Integer from) {
            return Integer.toBinaryString(from.intValue());

The function is simply converting the value of an Integer to its binary String representation.


4. Guava toMap()

4.Guava toMap()

Guava offers a static utility class pertaining to Map instances. Among others, it has two operations that can be used to convert a Set to a Map by applying the defined Guava’s Function.


The following snippet shows creating an immutable Map:


Map<Integer, String> immutableMap = Maps.toMap(set, function);

The following tests asserts that the set is properly converted:


public void givenStringSetAndSimpleMap_whenMapsToElementLength_thenCorrect() {
    Set set = new TreeSet(Arrays.asList(32, 64, 128));
    Map<Integer, String> immutableMap = Maps.toMap(set, function);
      && immutableMap.get(64).equals("1000000")
      && immutableMap.get(128).equals("10000000"));

The problem with the created map is that if an element is added to the source set, the derived map is not updated.


4. Guava asMap()

4.Guava asMap()

If we use the previous example and create a map using the Maps.asMap method:


Map<Integer, String> liveMap = Maps.asMap(set, function);

We’ll get a live map view – meaning that the changes to the originating Set will be reflected in the map as well:


public void givenStringSet_whenMapsToElementLength_thenCorrect() {
    Set<Integer> set = new TreeSet<Integer>(Arrays.asList(32, 64, 128));
    Map<Integer, String> liveMap = Maps.asMap(set, function);
            && liveMap.get(64).equals("1000000")
            && liveMap.get(128).equals("10000000"));
    assertTrue(liveMap.get(256).equals("100000000") && liveMap.size() == 4);

Note that the tests assert properly despite that we added an element through a set and looked it up inside the map.


5. Building Custom Live Map


When we talk of the Map View of a Set, we are basically extending the capability of the Set using a Guava Function.

当我们谈论MapView of a Set时,我们基本上是使用GuavaFunction来扩展Set的能力。

In the live Map view, changes to the Set should be updating the Map EntrySet in real time. We will create our own generic Map, sub-classing AbstractMap<K,V>, like so:

在实时Map视图中,对Set的更改应该实时更新MapEntrySet。我们将创建我们自己的通用Map,子类化 AbstractMap<K,V>,像这样。

public class GuavaMapFromSet<K, V> extends AbstractMap<K, V> {
    public GuavaMapFromSet(Set<K> keys, 
        Function<? super K, ? extends V> function) { 

Worthy of note is that the main contract of all sub-classes of AbstractMap is to implement the entrySet method, as we’ve done. We will then look at 2 critical parts of the code in the following sub-sections.


5.1. Entries


Another attribute in our Map will be entries, representing our EntrySet:


private Set<Entry<K, V>> entries;

The entries field will always be initialized using the input Set from the constructor:


public GuavaMapFromSet(Set<K> keys,Function<? super K, ? extends V> function) {

A quick note here – in order to maintain a live view, we will use the same iterator in the input Set for the subsequent Map‘s EntrySet.


In fulfilling the contract of AbstractMap<K,V>, we implement the entrySet method in which we then return entries:


public Set<java.util.Map.Entry<K, V>> entrySet() {
    return this.entries;

5.2. Cache


This Map stores the values obtained by applying Function to Set:


private WeakHashMap<K, V> cache;

6. The Set Iterator


We will use the input Set‘s iterator for the subsequent Map‘s EntrySet. To do this, we use a customized EntrySet as well as a customized Entry class.


6.1. The Entry Class


First, let’s see how a single entry in the Map will look like:


private class SingleEntry implements Entry<K, V> {
    private K key;
    public SingleEntry( K key) {
        this.key = key;
    public K getKey() {
        return this.key;
    public V getValue() {
        V value = GuavaMapFromSet.this.cache.get(this.key);
  if (value == null) {
      value = GuavaMapFromSet.this.function.apply(this.key);
      GuavaMapFromSet.this.cache.put(this.key, value);
  return value;
    public V setValue( V value) {
        throw new UnsupportedOperationException();

Clearly, in this code, we don’t allow modifying the Set from the Map View as a call to setValue throws an UnsupportedOperationException.

显然,在这段代码中,我们不允许从Map View修改Set,因为调用setValue会抛出UnsupportedOperationException。

Pay close attention to getValue – this is the crux of our live view functionality. We check cache inside our Map for the current key (Set element).

密切注意getValue – 这是我们实时视图功能的关键。我们在我们的Map中检查cache,查看当前的keySet元素)。

If we find the key, we return it, else we apply our function to the current key and obtain a value, then store it in cache.


This way, whenever the Set has a new element, the map is up to date since the new values are computed on the fly.


6.2. The EntrySet


We will now implement the EntrySet:


private class MyEntrySet extends AbstractSet<Entry<K, V>> {
    private Set<K> keys;
    public MyEntrySet(Set<K> keys) {
        this.keys = keys;
    public Iterator<Map.Entry<K, V>> iterator() {
        return new LiveViewIterator();
    public int size() {
        return this.keys.size();

We have fulfilled the contract for extending AbstractSet by overriding the iterator and size methods. But there’s more.


Remember the instance of this EntrySet will form the entries in our Map View. Normally, the EntrySet of a map simply returns a complete Entry for each iteration.


However, in our case, we need to use the iterator from the input Set to maintain our live view. We know well it will only return the Set‘s elements, so we also need a custom iterator.


6.3. The Iterator


Here is the implementation of our iterator for the above EntrySet:


public class LiveViewIterator implements Iterator<Entry<K, V>> {
    private Iterator<K> inner;
    public LiveViewIterator () {
        this.inner = MyEntrySet.this.keys.iterator();
    public boolean hasNext() {
        return this.inner.hasNext();
    public Map.Entry<K, V> next() {
        K key =;
        return new SingleEntry(key);
    public void remove() {
        throw new UnsupportedOperationException();

LiveViewIterator must reside inside the MyEntrySet class, this way, we can share the Set‘s iterator at initialization.


When looping through GuavaMapFromSet‘s entries using the iterator, a call to next simply retrieves the key from the Set‘s iterator and constructs a SingleEntry.


7. Putting It All Together


After stitching together what we have covered in this tutorial, let us take a replace the liveMap variable from the previous samples, and replace it with our custom map:


public void givenIntSet_whenMapsToElementBinaryValue_thenCorrect() {
    Set<Integer> set = new TreeSet<>(Arrays.asList(32, 64, 128));
    Map<Integer, String> customMap = new GuavaMapFromSet<Integer, String>(set, function);
      && customMap.get(64).equals("1000000")
      && customMap.get(128).equals("10000000"));

Changing the content of input Set, we will see that the Map updates in real time:


public void givenStringSet_whenMapsToElementLength_thenCorrect() {
    Set<Integer> set = new TreeSet<Integer>(Arrays.asList(32, 64, 128));
    Map<Integer, String> customMap = Maps.asMap(set, function);
      && customMap.get(64).equals("1000000")
      && customMap.get(128).equals("10000000"));
    assertTrue(customMap.get(256).equals("100000000") && customMap.size() == 4);

8. Conclusion


In this tutorial, we have looked at the different ways that one can leverage Guava operations and obtain a Map view from a Set by applying a Function.


The full implementation of all these examples and code snippets can be found in my Guava github project – this is an Eclipse based project, so it should be easy to import and run as it is.

所有这些例子和代码片段的完整实现可以在我的Guava github项目 – 这是一个基于Eclipse的项目,所以它应该很容易导入和运行,如实。