Guava应用Function从Set获取Map
1. 概述
在本教程中——我们将说明Guava的collect包中许多有用的功能之一:如何将 Function 应用于 Guava Set并获取Map。
我们将讨论两种方法——创建不可变Map和基于内置 guava 操作的实时Map,然后实现自定义实时Map实现。
2. 设置
首先,我们将Guava库添加为pom.xml 中的依赖项:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
快速说明 - 您可以在此处检查是否有更新的版本 。
3. 映射Function
让我们首先定义我们将应用于集合元素的函数:
Function<Integer, String> function = new Function<Integer, String>() {
@Override
public String apply(Integer from) {
return Integer.toBinaryString(from.intValue());
}
};
该函数只是将Integer的值转换为其二进制String表示形式。
4. toMap()
Guava 提供了一个与Map实例相关的静态实用程序类。其中,它有两个操作可用于通过应用定义的 Guava 的Function将Set转换为Map。
下面的代码片段展示了如何创建一个不可变的Map:
Map<Integer, String> immutableMap = Maps.toMap(set, function);
以下测试断言该集合已正确转换:
@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"));
}
创建映射的问题是,如果将元素添加到源集中,则派生映射不会更新。
5. asMap()
如果我们使用前面的示例并使用Maps.asMap方法创建Map:
Map<Integer, String> liveMap = Maps.asMap(set, function);
我们将获得一个实时Map视图——这意味着对原始 Set 的更改也将反映在Map中:
@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);
}
请注意,尽管我们通过集合添加了一个元素并在Map中查找它,但测试仍然正确断言。
6. 构建自定义实时Map
当我们谈论Set的Map视图时,我们基本上是在使用 Guava 函数扩展Set的功能。
在实时Map视图中,对Set的更改应该实时更新Map EntrySet。我们将创建自己的通用Map,子类AbstractMap<K,V>,如下所示:
public class GuavaMapFromSet<K, V> extends AbstractMap<K, V> {
public GuavaMapFromSet(Set<K> keys,
Function<? super K, ? extends V> function) {
}
}
值得注意的是,AbstractMap的所有子类的主要约定是实现entrySet方法,正如我们所做的那样。然后,我们将在以下小节中查看代码的 2 个关键部分。
5.1. Entries
我们Map中的另一个属性是entries,代表我们的EntrySet:
private Set<Entry<K, V>> entries;
entries字段将始终使用来自构造函数的输入Set进行初始化:
public GuavaMapFromSet(Set<K> keys,Function<? super K, ? extends V> function) {
this.entries=keys;
}
这里有一个快速说明——为了保持实时视图,我们将在输入Set中为后续Map的EntrySet使用相同的*iterator *。
在履行AbstractMap<K,V>的契约时,我们实现了entrySet方法,然后我们在其中返回entries:
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return this.entries;
}
5.2. 缓存
此Map存储通过将Function应用于Set 获得的值:
private WeakHashMap<K, V> cache;
6. 集合Iterator
我们将为后续Map的EntrySet使用输入Set的Iterator。为此,我们使用自定义的EntrySet以及自定义的Entry类。
6.1. Entry类
首先,让我们看看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();
}
}
显然,在这段代码中,我们不允许从Map 视图修改Set,因为调用setValue会引发UnsupportedOperationException。
密切关注getValue——这是我们实时取景功能的关键。我们在Map中检查cache中的当前key(Set元素)。
如果我们找到键,我们返回它,否则我们将我们的函数应用到当前键并获取一个值,然后将它存储在cache中。
这样,每当Set有一个新元素时,Map都是最新的,因为新值是动态计算的。
6.2. 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();
}
}
我们已经通过重写Iterator和size方法来履行扩展AbstractSet的合同。但还有更多。
请记住,此EntrySet的实例将构成我们Map视图中的Entry。通常,映射的EntrySet只是为每次迭代返回一个完整的Entry。
但是,在我们的例子中,我们需要使用来自输入Set的Iterator来维护我们的实时视图。我们很清楚它只会返回Set的元素,所以我们还需要一个自定义的Iterator。
6.3. Iterator
这是上述EntrySet的Iterator的实现:
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必须驻留在MyEntrySet类中,这样,我们可以在初始化时共享Set的Iterator。
当使用Iterator遍历GuavaMapFromSet的条目时,对next的调用只是从Set的Iterator中检索键并构造一个SingleEntry。
7. 把它们放在一起
在将我们在本教程中介绍的内容拼接在一起之后,让我们从前面的示例中替换liveMap变量,并将其替换为我们的自定义Map:
@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"));
}
更改输入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);
}