Javaのマップに重複キーを保存する方法は?

1。概要

このチュートリアルでは、重複するキーを持つマップ、つまり、単一のキーに複数の値を格納できるマップを処理するために使用できるオプションについて説明します。

2.標準マップ

Javaには、インターフェースMapのいくつかの実装があり、それぞれに独自の特殊性があります。

ただし、既存のJavaコアMap実装では、Mapが単一のキーに対して複数の値を処理することはできません

ご覧のとおり、同じキーに2つの値を挿入しようとすると、2番目の値が保存され、最初の値は削除されます。

また、(put(Kキー、V値)メソッドの適切な実装ごとに)返されます

Map 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
    
      map = new HashMap(); List 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"); 
    

ただし、この冗長なソリューションには複数の欠点があり、エラーが発生しやすくなります。これは、すべての値に対してコレクションをインスタンス化し、値を追加または削除する前にその存在を確認し、値が残っていないときに手動で削除する必要があることを意味します。

Java 8から、compute()メソッドを活用して改善することができます。

Map
    
      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. ApacheCommonsコレクション

いつものように、Apacheには私たちの問題に対する解決策があります。

最新リリースのインポートすることでのスタートをしてみましょう一般的なコレクション(今からCCを):

 org.apache.commons commons-collections4 4.1 

4.1。マルチマップ

org.apache.commons.collections4。MultiMapインターフェースは、各キーに対する値のコレクションを保持するマップを定義します。

これは、org.apache.commons.collections4.mapによって実装されます。MultiValueMapクラス。内部でボイラープレートのほとんどを自動的に処理します。

MultiMap map = new MultiValueMap(); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .contains("value1", "value2"); 

このクラスはCC3.2以降で使用できますが、スレッドセーフではなく、CC4.1では非推奨になっています。新しいバージョンにアップグレードできない場合にのみ使用してください。

4.2。MultiValuedMap

後継マルチマップがあるorg.apache.commons.collections4。MultiValuedMapインターフェース。すぐに使用できる複数の実装があります。

複数の値をArrayListに格納する方法を見てみましょう。ArrayListは重複を保持します。

MultiValuedMap map = new ArrayListValuedHashMap(); map.put("key1", "value1"); map.put("key1", "value2"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .containsExactly("value1", "value2", "value2"); 

または、HashSetを使用して、重複を削除することもできます。

MultiValuedMap map = new HashSetValuedHashMap(); map.put("key1", "value1"); map.put("key1", "value1"); assertThat((Collection) map.get("key1")) .containsExactly("value1"); 

上記の実装は両方ともスレッドセーフではありません

UnmodizableMultiValuedMapデコレータを使用してそれらを不変にする方法を見てみましょう。

@Test(expected = UnsupportedOperationException.class) public void givenUnmodifiableMultiValuedMap_whenInserting_thenThrowingException() { MultiValuedMap map = new ArrayListValuedHashMap(); map.put("key1", "value1"); map.put("key1", "value2"); MultiValuedMap immutableMap = MultiMapUtils.unmodifiableMultiValuedMap(map); immutableMap.put("key1", "value3"); } 

5.グアバMultimapは

Guavaは、JavaAPI用のGoogleコアライブラリです。

com.google.common.collect。マルチマップインターフェイスはバージョン2以降にあります。執筆時点では最新リリースは25ですが、バージョン23以降は、jreandroid25.0-jre25.0-android)の異なるブランチに分割されているため、引き続き使用します。例についてはバージョン23。

私たちのプロジェクトにGuavaをインポートすることから始めましょう:

 com.google.guava guava 23.0 

Guavaは、最初から複数の実装のパスをたどりました。

最も一般的なものはcom.google.common.collectです。ArrayListMultimap使用して、HashMapのは、に裏打ちされたArrayListのすべての値のために:

Multimap map = ArrayListMultimap.create(); map.put("key1", "value2"); map.put("key1", "value1"); assertThat((Collection) map.get("key1")) .containsExactly("value2", "value1"); 

いつものように、マルチマップインターフェースの不変の実装を好む必要があります:com.google.common.collect。ImmutableListMultimapおよびcom.google.common.collect。ImmutableSetMultimap

5.1。一般的なマップの実装

特定のMap実装が必要な場合、おそらくGuavaがすでに実装しているため、最初に行うことは、それが存在するかどうかを確認することです。

For example, we can use the com.google.common.collect.LinkedHashMultimap, which preserves the insertion order of keys and values:

Multimap map = LinkedHashMultimap.create(); map.put("key1", "value3"); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .containsExactly("value3", "value1", "value2"); 

Alternatively, we can use a com.google.common.collect.TreeMultimap, which iterates keys and values in their natural order:

Multimap map = TreeMultimap.create(); map.put("key1", "value3"); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .containsExactly("value1", "value2", "value3"); 

5.2. Forging Our Custom MultiMap

Many other implementations are available.

However, we may want to decorate a Map and/or a List not yet implemented.

Luckily, Guava has a factory method allowing us to do it: the Multimap.newMultimap().

6. Conclusion

We've seen how to store multiple values for a key in a Map in all the main existing ways.

Apache Commons CollectionsとGuavaの最も一般的な実装を調査しました。これらは、可能な場合はカスタムソリューションよりも優先されるはずです。

いつものように、完全なソースコードはGithubで入手できます。