Contents

Guava应用Function从Set获取Map

1. 概述

在本教程中——我们将说明Guavacollect包中许多有用的功能之一:如何将 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 的FunctionSet转换为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

当我们谈论SetMap视图时,我们基本上是在使用 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中为后续MapEntrySet使用相同的*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

我们将为后续MapEntrySet使用输入SetIterator。为此,我们使用自定义的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中的当前keySet元素)。

如果我们找到键,我们返回它,否则我们将我们的函数应用到当前键并获取一个值,然后将它存储在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();
    }
}

我们已经通过重写Iteratorsize方法来履行扩展AbstractSet的合同。但还有更多。

请记住,此EntrySet的实例将构成我们Map视图中的Entry。通常,映射的EntrySet只是为每次迭代返回一个完整的Entry

但是,在我们的例子中,我们需要使用来自输入SetIterator来维护我们的实时视图。我们很清楚它只会返回Set的元素,所以我们还需要一个自定义的Iterator

6.3. Iterator

这是上述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必须驻留在MyEntrySet类中,这样,我们可以在初始化时共享SetIterator

当使用Iterator遍历GuavaMapFromSet的条目时,对next的调用只是从SetIterator中检索键并构造一个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);
}