1概要

このチュートリアルでは、重複するキーを持つ

Map

、つまり単一のキーに複数の値を格納できる

Map

を処理するために使用できるオプションについて説明します。


2標準マップ

Javaには

Map

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

ただし、** 既存のJavaコアMap実装では、

Map

が単一のキーに対して複数の値を処理することは許可されていません。

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

それはまた返されます(


put(K key、V value )


方法):

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



インターフェースは、保持するMapを定義します。各キーに対する値のコレクション。

これは


org.apache.commons.collections4.map。

MultiValueMap


によって実装されています。

クラス、フードの下の定型文の大部分を自動的に処理します。

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



インターフェース。複数の実装が用意されています。

重複を保持する

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グアバ

マルチマップ


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



com.google.common.collect。

Multimap



インターフェースは、執筆時点で最新のリリースは25ですが、バージョン23以降は

jre



android



25.0-jre



25.0-android

)の異なるブランチに分割されているので、引き続きversionを使用します。私達の例のための23。

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

<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>23.0</version>
</dependency>

グアバは初めから複数の実装の道をたどった。

最も一般的なものは


com.google.common.collect。

ArrayListMultimap


です。すべての値に

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インターフェースの不変の実装を好むべきです。


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

特定の

Map

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

たとえば、


com.google.common.collect。

LinkedHashMultimap


を使用できます。]

は、キーと値の挿入順序を保持します。

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


を使用することもできます。

は、キーと値を自然な順序で繰り返します。

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にはそれを可能にするファクトリメソッドがあります。 util.Map-com.google.common.base.Supplier-[Multimap.newMultimap()]__。


6. 結論

既存の主な方法すべてで、マップ内のキーに複数の値を格納する方法を見てきました。

私たちは、Apache Commons CollectionsとGuavaの最も一般的な実装を探求しました。可能な限りカスタムソリューションよりも優先されるべきです。

いつものように、完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/java-collections-maps[over on Github]から入手できます。