読者です 読者をやめる 読者になる 読者になる

【Java】Mapの基本について

スポンサーリンク

過去に  配列・リスト・マップの使い方についての基本 という記事を書いていましたが Map については基本の基もない内容だったで Map 全般のことについて改めてまとめました。各具象クラスのことについてはまた別で書きます。

Mapとは

java.util.Mapインターフェースは1つのキーに対して1つの値を保持するコレクションであり、マップとしての振る舞いを規定しています。キーの重複は許可されておらず、格納できるデータは参照型のみになります。

そして実際には Map インターフェースを実装している具象クラスであるjava.util.HashMapjava.util.TreeMapを使用することになります。ただし、どの具象クラスを使う場合でも基本的に参照変数の型は次のようにインターフェースであるjava.util.Mapにしておきます。こうすることで具象クラスへの依存はオブジェクトの生成だけに限定することが可能です。

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

ちなみに1つのキーと1つの値と言っているのはいわゆるオブジェクトの事であり、String や Integer のデータを1つしか保持できない訳ではありません。値には List や Set や Map を持たせることもできますし、キーに自作クラスを指定することも可能です。

Map<String, List<String>> map = new HashMap<>();  // 値にListを指定
Map<String, Set<Integer>> map = new HashMap<>();  // 値にSetを指定
Map<String, Map<Integer, Object>> map = new HashMap<>();  // 値にMapを指定
Map<Person, List<String>> map = new HashMap<>();  // キーにPersonという独自クラスを指定

 HashMapの基本動作と自作クラスをキーに指定する注意点など

Mapの生成と初期値

Mapの生成は以下の通りです。

Map map = new HashMap();  // 型引数を省略した場合はObject型になる(JDK1.4まで)
Map<データ型, データ型> マップ名 = new HashMap<データ型, データ型>();  // 総称型の利用(JDK1.5)
Map<データ型, データ型> マップ名 = new HashMap<省略>();  // ダイアモンド演算子で型引数を省略(JDK1.7)

List の場合はArrays.asList()などを使用して生成と同時に値を設定することが可能ですが、Map にはそのようなメソッドがありません。 方法としては以下のように匿名クラスと初期化ブロックを使用することで可能になりますが、この場合はダイアモンド演算子が使えないので型引数が省略できません。プロダクションのコードに使用するかというと少し微妙かもしれないです。

Map<String, String> map = new HashMap<String, String>() {
    {
        put("key1","value1");
        put("key2","value2");
    }
};

Mapの基本メソッド

具象クラスで実装が必要な基本メソッドです。

メソッド 機能
size() Mapにマッピングされているキーと値の数を返す
isEmpty() Mapがキーと値のマッピングを保持しない場合はtrueを返す
containsKey(Object key) 指定したキーが含まれている場合はtrueを返す
containsValue(Object value) 指定した値が含まれている場合はtrueを返す
get(Object key) 指定したキーに対応する値を返す(キーが存在しない場合はnull)
put(K key, V value) 指定したキーと値をマッピングする(キーが存在した場合は上書き)
remove(Object key) Mapからキーのマッピングを削除する
putAll(Map<K, V> m) 指定したMapの全てのマッピングをコピー(1件ずつputするのと同様)
clear() Mapからマッピングをすべて削除する
keySet() マップに含まれるキーのSetを返す
values() マップに含まれる値のCollectionを返す
entrySet() マップに含まれるキーと値のSetを返す
equals(Object o) 指定したオブジェクトとMapを比較(実装クラスが同じ必要はない)
hashCode() Mapのハッシュコード値を返す

JDK1.8から追加された Map インターフェースの Default メソッドです。

メソッド 機能
compute(K key, BiFunction<K, V, V> f) 指定したキーと現在の値から関数により新しい値を求める
computeIfAbsentl(K key, Function<K, V> f) 指定したキーが存在しない場合(null含む)関数を実行
computeIfPresent(K key, BiFunction<K, V, V> f) 指定したキーが存在していてnull以外の場合関数を実行
forEach(BiConsumer<K, V> action) 全てのエントリーに対して指定した関数を実行する
getOrDefault(Object key, V defaultValue) getとの違いはキーが存在しない場合の戻り値を指定可能
putIfAbsent(K key, V value) 指定したキーが関連付けられてない場合put(null含む)
merge(K key, V val, BiFunction<V, V, V> f) putIfAbsent + 値が存在した時に関数を実行する
remove(Object key, Object value) 指定したキーと値が存在した場合のみ削除する
replace(K key, V value) 指定したキーが存在した場合のみ置換する
replace(K key, V value, V newValue) 指定したキーと値がマッピングされている場合のみ置換する
replaceAll(BiFunction<K, V, V> f) 各エントリーのキーと値から関数を実行した結果で置換する

 Map (Java Platform SE 8)

Mapの繰り返し処理

Map は List や Set と異なり Collection や Iterable インターフェースを継承していないので、基本的には全ての要素に対して順次処理をしていくことができません。(JDK1.8から実装されたforEachメソッドを使用すれば可能だが、これも内部的にはentrySetを使用しているので別の話し)

そのため全ての要素に対して順次処理を行いたい場合は、keySet()でキーの Set を、values()で値の Collection を、entrySetでキーと値の Set を取得できるので、それに対して順次処理を実装する形になります。

Map<String, Integer> map = new HashMap<>();
for (String hoge : map.keySet()) {
    // 処理; 
}
for (Integer fuga : map.values()) {
    // 処理;
}
for (Map.Entry<String, Integer> bar : map.entrySet()) {
    bar.getKey();  // キーを取得
    bar.getValue();  // 値を取得
}
map.forEach((key, val) -> 処理);

注意点としては、keySet()で取得した Set インスタンスに対して要素の削除をすると、取得元の Map からも対応するキーと値が削除されます。また、逆に Map からキーをremove()で削除した場合も Set インスタンスから削除されます。

追加はどうなるのかというと、Map にput()でキーと値を追加した場合は Set にも追加されますが、Set への追加はサポートされていませんのでUnsupportedOperationExceptionが発生します。Set を返すといっても HashSet などではなく、HashMap に実装されている AbstractSet を継承した KeySet というクラスが返されるためです。keySet()で説明しましたがvalues()entrySet()も同様です。