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

最后修改: 2016年 6月 17日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

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.

在本教程中,我们将说明Guavacollect包中许多有用的功能之一。如何将一个函数应用于Guava的Set并获得一个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.

我们将讨论两种方法–基于内置的guava操作创建一个不可变的地图和一个实时地图,然后是一个自定义的实时Map实现。

2. Setup

2.设置

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

首先,我们将在pom.xml中添加Guava库作为一个依赖项:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

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

一个简短的说明–你可以检查这里是否有较新的版本

3. The Mapping Function

3.映射功能

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

让我们首先定义我们将应用于集合元素的函数。

    Function<Integer, String> function = new Function<Integer, String>() {
        @Override
        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.

该函数只是将一个Integer的值转换为其二进制String表示。

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.

Guava提供了一个与Map实例相关的静态实用类。其中,它有两个操作,通过应用定义的Guava的Function,可以将Set转换为Map

The following snippet shows creating an immutable Map:

下面的片段显示了创建一个不可变的Map

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

The following tests asserts that the set is properly converted:

下面的测试断言,这组数据已经正确转换。

@Test
public void givenStringSetAndSimpleMap_whenMapsToElementLength_thenCorrect() {
    Set set = new TreeSet(Arrays.asList(32, 64, 128));
    Map<Integer, String> immutableMap = Maps.toMap(set, function);
    assertTrue(immutableMap.get(32).equals("100000")
      && 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:

如果我们使用前面的例子,用Maps.asMap方法创建一个地图。

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:

我们会得到实时地图视图–这意味着对原发集的改变也会反映在地图上。

@Test
public void givenStringSet_whenMapsToElementLength_thenCorrect() {
    Set<Integer> set = new TreeSet<Integer>(Arrays.asList(32, 64, 128));
    Map<Integer, String> liveMap = Maps.asMap(set, function);
    assertTrue(liveMap.get(32).equals("100000")
            && liveMap.get(64).equals("1000000")
            && liveMap.get(128).equals("10000000"));
    
    set.add(256);
    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

5.构建自定义实时地图

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.

值得注意的是,AbstractMap的所有子类的主要契约是实现entrySet方法,正如我们所做的。然后我们将在下面的小节中看一下代码的2个关键部分。

5.1. Entries

5.1.参赛作品

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

我们的Map中的另一个属性将是entries,代表我们的EntrySet:

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

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

entries字段将始终使用来自构造器的输入Set来初始化:

public GuavaMapFromSet(Set<K> keys,Function<? super K, ? extends V> function) {
    this.entries=keys;
}

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.

在此简单说明一下–为了保持实时视图,我们将在输入的Set中使用相同的iterator,用于后续MapEntrySet。

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

在履行AbstractMap<K,V>的契约时,我们实现了entrySet方法,然后在其中返回entries

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

5.2. Cache

5.2.缓存

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

这个Map存储了通过应用FunctionSet:获得的值。

private WeakHashMap<K, V> cache;

6. The Set Iterator

6.设置迭代器

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.

我们将把输入的Setiterator用于后续MapEntrySet。为了做到这一点,我们使用了一个定制的EntrySet以及一个定制的Entry类。

6.1. The Entry Class

6.1.Entry

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

首先,让我们看看Map中的单个条目会是什么样子。

private class SingleEntry implements Entry<K, V> {
    private K key;
    public SingleEntry( K key) {
        this.key = key;
    }
    @Override
    public K getKey() {
        return this.key;
    }
    @Override
    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;
    }
    @Override
    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.

如果我们找到了键,我们就返回它,否则我们将我们的函数应用于当前的键并获得一个值,然后将其存储在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.

这样,每当Set有一个新的元素,地图就会是最新的,因为新的值是即时计算的。

6.2. The EntrySet

6.2.EntrySet

We will now implement the EntrySet:

我们现在将实现EntrySet

private class MyEntrySet extends AbstractSet<Entry<K, V>> {
    private Set<K> keys;
    public MyEntrySet(Set<K> keys) {
        this.keys = keys;
    }
    @Override
    public Iterator<Map.Entry<K, V>> iterator() {
        return new LiveViewIterator();
    }
    @Override
    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.

通过覆盖iteratorsize方法,我们已经履行了扩展AbstractSet的契约。但还有更多。

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.

请记住,这个EntrySet的实例将在我们的MapView中形成entries。通常,地图的EntrySet只是为每次迭代返回一个完整的Entry

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.

然而,在我们的案例中,我们需要使用来自输入Setiterator来维护我们的实时视图我们很清楚它将只返回Set的元素,所以我们也需要一个自定义的iterator

6.3. The Iterator

6.3.迭代器

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

下面是我们对上述EntrySetiterator的实现。

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

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

LiveViewIterator必须位于MyEntrySet类内,这样,我们可以在初始化时共享Setiterator

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.

当使用iterator循环浏览GuavaMapFromSet的条目时,对next的调用只是从Setiterator中获取键,并构建一个SingleEntry

7. Putting It All Together

7.把一切都放在一起

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:

将本教程中所涉及的内容拼接在一起后,让我们把前面样本中的liveMap变量替换掉,用我们的自定义地图替换。

@Test
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);
    
    assertTrue(customMap.get(32).equals("100000")
      && 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:

改变输入Set的内容,我们将看到Map的实时更新。

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

8. Conclusion

8.结论

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.

在本教程中,我们研究了利用Guava操作和通过应用FunctionSet获得Map视图的不同方法。

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的项目,所以它应该很容易导入和运行,如实。