覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

com.google.common.collect.Multimap

Guavacom.google.common.collect.Multimapの利用サンプルです。

Multimapは、特定のkeyに対して値を複数持つことができるデータ構造です。

業務処理などでは、このようなデータ構造が必要となるケースが少なくないと思います。非常に強力なデータ構造です。
(Java標準で提供してくれてもいいような気がするんですが・・)


Multimapを使わない場合

Multimapを使わずに特定keyに対して値を複数持つ場合、以下のようなものを定義することになると思います

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

このMapに対する操作は(極端に書けば)以下のようになると思います

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

if (!normalMap.containsKey("key1")) {
        normalMap.put("key1", new ArrayList<String>());
}
normalMap.get("key1").add("value1");

if (!normalMap.containsKey("key1")) {
    normalMap.put("key1", new ArrayList<String>());
}
normalMap.get("key1").add("value2");

if (!normalMap.containsKey("key1")) {
    normalMap.put("key1", new ArrayList<String>());
}
normalMap.get("key1").add("value3");

if (!normalMap.containsKey("key2")) {
    normalMap.put("key2", new ArrayList<String>());
}
normalMap.get("key2").add("data1");

if (!normalMap.containsKey("key3")) {
    normalMap.put("key3", new ArrayList<String>());
}
normalMap.get("key3").add("Taro");

激しくめんどくさいです。

こんな時はMultimapに頼りましょう。 以下例は、ArrayListのようなデータ型で管理されるvalueを持つMultimapとなっています。


keyに対してvalueを追加する

とりあえずMultimapを生成して、keyに対してvalueを追加(紐づけ)します

■サンプルコード

Multimap<String, String> multiMap = ArrayListMultimap.create();
multiMap.put("key1", "value1");
multiMap.put("key1", "value2");
multiMap.put("key1", "value3");
multiMap.put("key2", "data1");
multiMap.put("key3", "Taro");

System.out.println("Multimap#toString: " + multiMap);

■実行結果

Multimap#toString: {key1=[value1, value2, value3], key2=[data1], key3=[Taro]}

非常にシンプルに書けます


各個別の要素にアクセスする

通常のMapであれば、valueのList内の各要素全てにアクセスしようとすると、結構めんどくさいです。
が、Multimapであれば以下のようにMultiMap#entriesを利用することで簡単に(全key紐づく全valueに対してフラットに)アクセスできます

■サンプルコード

for (Entry<String, String> entry : multiMap.entries()) {
    System.out.println("MultiMap::" + entry.getKey() + " -> " + entry.getValue());
}

■実行結果

MultiMap::key1 -> value1
MultiMap::key1 -> value2
MultiMap::key1 -> value3
MultiMap::key2 -> data1
MultiMap::key3 -> Taro


特定のkeyに複数のvalueをまとめて追加する

MultiMap#putAllで特定のkeyに複数のvalueをまとめて追加できます

■サンプルコード

System.out.println("--- Before Multimap#toString: " + multiMap);

List<String> values = Arrays.asList("data2", "data3", "data4", "data1", "data2");
multiMap.putAll("key2", values);

System.out.println("--- After  Multimap#toString: " + multiMap);

■実行結果

--- Before Multimap#toString: {key1=[value1, value2, value3], key2=[data1], key3=[Taro]}
--- After  Multimap#toString: {key1=[value1, value2, value3], key2=[data1, data2, data3, data4, data1, data2], key3=[Taro]}


特定のvalueが含まれているかを個別にチェックする

keyに紐づいたList内の要素に特定の値が含まれているかをチェックするのは、普通のMapを使っている場合には、これまた面倒です。
Multimapでは、Multimap#containsValueでチェックできます

■サンプルコード

System.out.println("Multimap#toString: " + multiMap);

// 特定のvalueが存在するかを個別にチェックできる
System.out.println("Multimap#containsValue(data3)   -> " + multiMap.containsValue("data3"));
System.out.println("Multimap#containsValue(data999) -> " + multiMap.containsValue("data999"));

■実行結果

Multimap#toString: {key1=[value1, value2, value3], key2=[data1, data2, data3, data4, data1, data2], key3=[Taro]}
Multimap#containsValue(data3)   -> true
Multimap#containsValue(data999) -> false


特定のkeyとvalueの組み合わせが含まれているかをチェックする

Multimapでは、Multimap#containsEntryで特定のkeyとvalueの組み合わせのエントリが存在しているかをチェックできます

■サンプルコード

System.out.println("Multimap#toString: " + multiMap);
        
// 特定のkeyとvalueの組み合わせが存在するかをチェックできる
System.out.println("Multimap#containsEntry(key1, data3)   -> " + multiMap.containsEntry("key1", "data3"));
System.out.println("Multimap#containsEntry(key2, data3)   -> " + multiMap.containsEntry("key2", "data3"));
System.out.println("Multimap#containsEntry(key99, value1) -> " + multiMap.containsEntry("key99", "value1"));

■実行結果

Multimap#toString: {key1=[value1, value2, value3], key2=[data1, data2, data3, data4, data1, data2], key3=[Taro]}
Multimap#containsEntry(key1, data3)   -> false
Multimap#containsEntry(key2, data3)   -> true
Multimap#containsEntry(key99, value1) -> false


エントリ数分のkeyを取得する

Multimap#keysを使うことで、エントリ数文のkeyをMultisetで受け取ることができます

■サンプルコード

System.out.println("Multimap#toString: " + multiMap);

// Multimap#keys で 全keyをMultisetで受け取れる(個別のvalue分のkeyが格納されている)
Multiset<String> keys = multiMap.keys();
System.out.println("Multimap#keys -> " + keys);

■実行結果

Multimap#keys -> [key1 x 3, key2 x 6, key3]


特定のkeyとvalueの組み合わせのエントリを削除する

Multimap#removeを使うことで、指定のkeyとvalueの組み合わせのエントリだけを削除することができます。
対象の組のエントリが複数存在する場合は、1エントリだけ削除されます。

■サンプルコード

System.out.println("--- Before Multimap#toString: " + multiMap);

// MultiMap#remove(key, value) で 指定のkeyとvalueの組み合わせの要素だけ削除する
// 存在する組み合わせなので戻り値trueが返る
System.out.println("Multimap#remove(key1, value3) -> " + multiMap.remove("key1", "value3"));

// 存在しない組み合わせなので戻り値falseが返る
System.out.println("Multimap#remove(key2, value3) -> " + multiMap.remove("key2", "value3"));

// 対象の組み合わせが複数存在しているものを削除すると1組だけ削除される
System.out.println("Multimap#remove(key2, data1)  -> " + multiMap.remove("key2", "data1"));

System.out.println("--- After  Multimap#toString: " + multiMap);

■実行結果

--- Before Multimap#toString: {key1=[value1, value2, value3], key2=[data1, data2, data3, data4, data1, data2], key3=[Taro]}
Multimap#remove(key1, value3) -> true
Multimap#remove(key2, value3) -> false
Multimap#remove(key2, data1)  -> true
--- After  Multimap#toString: {key1=[value1, value2], key2=[data2, data3, data4, data1, data2], key3=[Taro]}


特定のkeyに紐づく全エントリを削除する

Multimap#removeAllを使うことで、指定のkeyに紐づく全エントリを削除することができます。

■サンプルコード

System.out.println("--- Before Multimap#toString: " + multiMap);
        
// MultiMap#removeAll(key) で 指定のkeyの要素が全て削除される
System.out.println("Multimap#removeAll(key2) -> " + multiMap.removeAll("key2"));

System.out.println("--- After  Multimap#toString: " + multiMap);

■実行結果

--- Before Multimap#toString: {key1=[value1, value2], key2=[data2, data3, data4, data1, data2], key3=[Taro]}
Multimap#removeAll(key2) -> [data2, data3, data4, data1, data2]
--- After  Multimap#toString: {key1=[value1, value2], key3=[Taro]}


特定のkeyに紐づく値をまとめて置き換える

Multimap#replaceValuesを使うことで、指定のkeyに紐づくエントリを置き換えることができます

■サンプルコード

System.out.println("--- Before Multimap#toString: " + multiMap);

List<String> valuesNew = Arrays.asList("Jiro", "Ken", "John");
multiMap.replaceValues("key3", valuesNew);

System.out.println("--- After  Multimap#toString: " + multiMap);

■実行結果

--- Before Multimap#toString: {key1=[value1, value2], key3=[Taro]}
--- After  Multimap#toString: {key1=[value1, value2], key3=[Jiro, Ken, John]}


java.util.Mapに変換する

Multimap#asMapを使うことで、java.util.Mapで受け取ることができます

■サンプルコード

System.out.println("--- Src Multimap#toString: " + multiMap);

Map<String, Collection<String>> map = multiMap.asMap();

System.out.println("--- Dest Map#toString: " + map);

■実行結果

--- Src Multimap#toString: {key1=[value1, value2], key3=[Jiro, Ken, John]}
--- Dest Map#toString: {key1=[value1, value2], key3=[Jiro, Ken, John]}



試したソースコードの全体は以下の通りです

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;

public class MultimapClient {

    public static void main(String[] args) {
        // Java標準のやり方(対象keyに関連付けが無い場合はValueをnewして設定する処理を書く必要がある)
        Map<String, List<String>> normalMap = new HashMap<String, List<String>>();

        if (!normalMap.containsKey("key1")) {
                normalMap.put("key1", new ArrayList<String>());
        }
        normalMap.get("key1").add("value1");

        if (!normalMap.containsKey("key1")) {
            normalMap.put("key1", new ArrayList<String>());
        }
        normalMap.get("key1").add("value2");

        if (!normalMap.containsKey("key1")) {
            normalMap.put("key1", new ArrayList<String>());
        }
        normalMap.get("key1").add("value3");

        if (!normalMap.containsKey("key2")) {
            normalMap.put("key2", new ArrayList<String>());
        }
        normalMap.get("key2").add("data1");

        if (!normalMap.containsKey("key3")) {
            normalMap.put("key3", new ArrayList<String>());
        }
        normalMap.get("key3").add("Taro");


        for (Map.Entry<String, List<String>> entry : normalMap.entrySet()) {
            System.out.println("HashMap::" + entry.getKey() + " -> " + entry.getValue());
        }
        System.out.println("-------------------");

        // MultiMapを使用したやり方
        Multimap<String, String> multiMap = ArrayListMultimap.create();
        multiMap.put("key1", "value1");
        multiMap.put("key1", "value2");
        multiMap.put("key1", "value3");
        multiMap.put("key2", "data1");
        multiMap.put("key3", "Taro");

        System.out.println("Multimap#toString: " + multiMap);

        // MultiMap#entries を使えば 全要素に個別にアクセス可能
        for (Entry<String, String> entry : multiMap.entries()) {
            System.out.println("MultiMap::" + entry.getKey() + " -> " + entry.getValue());
        }

        // putAllで特定のkeyに複数のvalueをまとめて追加できる
        System.out.println("\n### Multimap#putAll");
        System.out.println("--- Before Multimap#toString: " + multiMap);

        List<String> values = Arrays.asList("data2", "data3", "data4", "data1", "data2");
        multiMap.putAll("key2", values);

        System.out.println("--- After  Multimap#toString: " + multiMap);

        System.out.println("\n### Multimap#containsValue");
        System.out.println("Multimap#toString: " + multiMap);
        // 特定のvalueが存在するかを個別にチェックできる
        System.out.println("Multimap#containsValue(data3)   -> " + multiMap.containsValue("data3"));
        System.out.println("Multimap#containsValue(data999) -> " + multiMap.containsValue("data999"));


        System.out.println("\n### Multimap#containsEntry");
        System.out.println("Multimap#toString: " + multiMap);
        // 特定のkeyとvalueの組み合わせが存在するかをチェックできる
        System.out.println("Multimap#containsEntry(key1, data3)   -> " + multiMap.containsEntry("key1", "data3"));
        System.out.println("Multimap#containsEntry(key2, data3)   -> " + multiMap.containsEntry("key2", "data3"));
        System.out.println("Multimap#containsEntry(key99, value1) -> " + multiMap.containsEntry("key99", "value1"));

        System.out.println("\n### Multimap#keys");
        System.out.println("Multimap#toString: " + multiMap);

        // Multimap#keys で 全keyをMultisetで受け取れる(個別のvalue分のkeyが格納されている)
        Multiset<String> keys = multiMap.keys();
        System.out.println("Multimap#keys -> " + keys);

        System.out.println("\n### Multimap#remove");
        System.out.println("--- Before Multimap#toString: " + multiMap);

        // MultiMap#remove(key, value) で 指定のkeyとvalueの組み合わせの要素だけ削除する
        // 存在する組み合わせなので戻り値trueが返る
        System.out.println("Multimap#remove(key1, value3) -> " + multiMap.remove("key1", "value3"));
        // 存在しない組み合わせなので戻り値falseが返る
        System.out.println("Multimap#remove(key2, value3) -> " + multiMap.remove("key2", "value3"));
        // 対象の組み合わせが複数存在しているものを削除すると1組だけ削除される
        System.out.println("Multimap#remove(key2, data1)  -> " + multiMap.remove("key2", "data1"));

        System.out.println("--- After  Multimap#toString: " + multiMap);


        System.out.println("\n### Multimap#removeAll");
        System.out.println("--- Before Multimap#toString: " + multiMap);

        // MultiMap#removeAll(key) で 指定のkeyの要素が全て削除される
        System.out.println("Multimap#removeAll(key2) -> " + multiMap.removeAll("key2"));

        System.out.println("--- After  Multimap#toString: " + multiMap);

        // replaceValuesで特定のkeyに紐づく値をまとめて置き換える
        System.out.println("\n### Multimap#replaceValues");
        System.out.println("--- Before Multimap#toString: " + multiMap);

        List<String> valuesNew = Arrays.asList("Jiro", "Ken", "John");
        multiMap.replaceValues("key3", valuesNew);

        System.out.println("--- After  Multimap#toString: " + multiMap);

        // asMapでjava.util.Mapで受け取ることができる
        System.out.println("\n### Multimap#map");
        System.out.println("--- Src Multimap#toString: " + multiMap);

        Map<String, Collection<String>> map = multiMap.asMap();

        System.out.println("--- Dest Map#toString: " + map);
    }
}



関連エントリ