如何在Java中的Map中存儲重複鍵?

1.概述

在本教程中,我們將探討可用的選項,這些選項可用於處理帶有重複鍵的Map ,或者換言之,允許為單個鍵存儲多個值的Map

2.標準Map

Java具有接口Map幾種實現,每種實現都有自己的特殊性。

但是,現有的Java核心Map實現都不允許Map處理單個key的多個值

如我們所見,如果我們嘗試為同一個鍵插入兩個值,則將存儲第二個值,而將第一個值刪除。

它也將被返回(通過[put(K key, V value)](https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#put-K-V-)方法的每個適當實現):

Map<String, String> map = new HashMap<>();

 assertThat(map.put("key1", "value1")).isEqualTo(null);

 assertThat(map.put("key1", "value2")).isEqualTo("value1");

 assertThat(map.get("key1")).isEqualTo("value2");

那我們怎樣才能達到期望的行為呢?

3.收集價值

顯然,對每個Map值使用Collection完成此工作:

Map<String, List<String>> map = new HashMap<>();

 List<String> list = new ArrayList<>();

 map.put("key1", list);

 map.get("key1").add("value1");

 map.get("key1").add("value2");



 assertThat(map.get("key1").get(0)).isEqualTo("value1");

 assertThat(map.get("key1").get(1)).isEqualTo("value2");

但是,這種冗長的解決方案具有多個缺點,並且容易出錯。這意味著我們需要為每個值實例化一個Collection ,在添加或刪除值之前檢查其是否存在,在沒有剩餘值時手動將其刪除,等等。

從Java 8開始,我們可以利用compute()方法並對其進行改進:

Map<String, List<String>> map = new HashMap<>();

 map.computeIfAbsent("key1", k -> new ArrayList<>()).add("value1");

 map.computeIfAbsent("key1", k -> new ArrayList<>()).add("value2");



 assertThat(map.get("key1").get(0)).isEqualTo("value1");

 assertThat(map.get("key1").get(1)).isEqualTo("value2");

儘管這是一個值得知道的事情,但除非有充分的理由不這樣做,否則我們應避免這樣做,例如限制性的公司政策禁止我們使用第三方庫。

否則,在編寫我們自己的自定義Map實現並重新發明輪子之前,我們應該在多個可用的現成選項中進行選擇。

4. Apache Commons集合

像往常一樣, Apache為我們的問題提供了解決方案。

讓我們從導入最新版本的Common Collections (從現在開始CC)開始:

<dependency>

 <groupId>org.apache.commons</groupId>

 <artifactId>commons-collections4</artifactId>

 <version>4.1</version>

 </dependency>

4.1。多圖

[org.apache.commons.collections4. **MultiMap**](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/MultiMap.html)接口定義一個Map,該Map包含每個鍵的值集合。

它是由[org.apache.commons.collections4.map. **MultiValueMap**](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/MultiValueMap.html)實現的[org.apache.commons.collections4.map. **MultiValueMap**](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/MultiValueMap.html)類,可自動處理引擎蓋下的大部分樣板:

MultiMap<String, String> map = new MultiValueMap<>();

 map.put("key1", "value1");

 map.put("key1", "value2");

 assertThat((Collection<String>) map.get("key1"))

 .contains("value1", "value2");

儘管此類從CC 3.2開始可用,但它不是線程安全的,並且在CC 4.1中已棄用。僅當我們無法升級到較新版本時,才應使用它。

4.2。 MultiValuedMap

MultiMap的後續版本是[org.apache.commons.collections4. **MultiValuedMap**](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/MultiValuedMap.html)接口。它具有多種可供使用的實現方式。

讓我們看看如何將多個值存儲到ArrayList ,該ArrayList保留重複項:

MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();

 map.put("key1", "value1");

 map.put("key1", "value2");

 map.put("key1", "value2");

 assertThat((Collection<String>) map.get("key1"))

 .containsExactly("value1", "value2", "value2");

另外,我們可以使用HashSet ,它刪除重複項:

MultiValuedMap<String, String> map = new HashSetValuedHashMap<>();

 map.put("key1", "value1");

 map.put("key1", "value1");

 assertThat((Collection<String>) map.get("key1"))

 .containsExactly("value1");

以上兩種實現都不是線程安全的

讓我們看看如何使用UnmodifiableMultiValuedMap裝飾器使它們不可變:

@Test(expected = UnsupportedOperationException.class)

 public void givenUnmodifiableMultiValuedMap_whenInserting_thenThrowingException() {

 MultiValuedMap<String, String> map = new ArrayListValuedHashMap<>();

 map.put("key1", "value1");

 map.put("key1", "value2");

 MultiValuedMap<String, String> immutableMap =

 MultiMapUtils.unmodifiableMultiValuedMap(map);

 immutableMap.put("key1", "value3");

 }

5.番石榴Multimap

番石榴是Java API的Google核心庫。

[com.google.common.collect. **Multimap**](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/Multimap.html)從版本2開始就有[**com.google.common.collect. Multimap**](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/Multimap.html)接口。在撰寫本文時,最新版本是25,但是由於在版本23之後已將其拆分為jreandroid25.0-jre25.0-android )的不同分支,因此我們仍將使用我們的示例版本23。

讓我們從在項目中導入番石榴開始:

<dependency>

 <groupId>com.google.guava</groupId>

 <artifactId>guava</artifactId>

 <version>23.0</version>

 </dependency>

從一開始,番石榴就遵循了多種實現方式。

最常見的是[com.google.common.collect. **ArrayListMultimap**](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/ArrayListMultimap.html) ,它對每個值使用由ArrayList支持的HashMap

Multimap<String, String> map = ArrayListMultimap.create();

 map.put("key1", "value2");

 map.put("key1", "value1");

 assertThat((Collection<String>) map.get("key1"))

 .containsExactly("value2", "value1");

與往常一樣,我們應該首選Multimap接口的不變實現: [com.google.common.collect. **ImmutableListMultimap**](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/ImmutableListMultimap.html)[com.google.common.collect. **ImmutableSetMultimap**](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/ImmutableSetMultimap.html)

5.1。通用地圖實施

當我們需要特定的Map實現時,首先要做的就是檢查它是否存在,因為Guava可能已經實現了。

例如,我們可以使用[com.google.common.collect. **LinkedHashMultimap**](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/LinkedHashMultimap.html) ,保留鍵和值的插入順序:

Multimap<String, String> map = LinkedHashMultimap.create();

 map.put("key1", "value3");

 map.put("key1", "value1");

 map.put("key1", "value2");

 assertThat((Collection<String>) map.get("key1"))

 .containsExactly("value3", "value1", "value2");

另外,我們可以使用[com.google.common.collect. **TreeMultimap**](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/TreeMultimap.html) ,以其自然順序迭代鍵和值:

Multimap<String, String> map = TreeMultimap.create();

 map.put("key1", "value3");

 map.put("key1", "value1");

 map.put("key1", "value2");

 assertThat((Collection<String>) map.get("key1"))

 .containsExactly("value1", "value2", "value3");

5.2。鍛造我們的自定義MultiMap

還有許多其他實現方式。

但是,我們可能要裝飾尚未實現的Map和/或List

幸運的是,Guava有一個工廠方法允許我們執行此操作: [Multimap.newMultimap()](https://google.github.io/guava/releases/23.0/api/docs/com/google/common/collect/Multimaps.html#newMultimap-java.util.Map-com.google.common.base.Supplier-)

六,結論

我們已經看到瞭如何以現有的所有主要方式在鍵中存儲鍵的多個值。

我們探索了Apache Commons Collections和Guava的最受歡迎的實現,在可能的情況下,它們應該比自定義解決方案更可取。

與往常一樣,完整的源代碼可以在Github上找到