如何在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之後已將其拆分為jre
和android
( 25.0-jre
和25.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上找到。