1. はじめに
現実世界では、各ユニークな「キー」に「値」を対応づける場面がよくあります。電話帳は電話番号と人名を保管し、辞書は単語とその訳語を結びつけ、成績表では各学生に名前と対応する点数があります。
Java ではこのような課題に対してインターフェース Map があります。これは「キーと値」のペアを保持するコレクション(key-value pair)です。
Map の主な性質:
- 各キーは一意(重複不可)。
- 1 つのキーに対応する値は 1 つだけ。
- 値は重複してもよい。
たとえ話で考えてみましょう。リスト(List)は食堂の行列のようなもの(各自が自分の位置にいて、番号で参照できる)で、マップ(Map)はロッカーのようなものです。各ロッカーには番号(キー)があり、その中にそれぞれの品(値)が入っています。
Map インターフェース: 基本操作
インターフェース Map はキーと値のペアを扱うための基本メソッドを宣言します:
| メソッド | 説明 |
|---|---|
|
キーに対応する値を追加/置換 |
|
キーで値を取得 |
|
キーでエントリを削除 |
|
そのキーが存在するか確認 |
|
その値が存在するか確認 |
|
マップ内のエントリ数 |
|
Map が空か確認 |
|
すべてのエントリを削除 |
K と V はジェネリック型パラメータです。K(Key)はキーの型、V(Value)は値の型を表します。
3. HashMap クラス: キーによる高速アクセス
HashMap とは?
HashMap はインターフェース Map の最も一般的な実装です。キーで値に高速アクセスできます。
重要: HashMap は要素の順序を保証しません。特定の順序でキーを追加しても、走査時には別の順序になることがあります。
HashMap の作成方法
import java.util.HashMap;
import java.util.Map;
public class Example {
public static void main(String[] args) {
// マップを作成: キーは String、値は Integer
Map<String, Integer> ages = new HashMap<>();
// 要素を追加
ages.put("ヴァーシャ", 25);
ages.put("ペーチャ", 30);
ages.put("マーシャ", 22);
// キーで値を取得
int vasyaAge = ages.get("ヴァーシャ");
System.out.println("ヴァーシャの年齢: " + vasyaAge); // 25
// キーの存在を確認
if (ages.containsKey("マーシャ")) {
System.out.println("マーシャはリストにいます!");
}
// 要素を削除
ages.remove("ペーチャ");
// すべてのキーと値のペアを走査
for (String name : ages.keySet()) {
System.out.println(name + ": " + ages.get(name));
}
}
}
出力:
ヴァーシャの年齢: 25
マーシャはリストにいます!
ヴァーシャ: 25
マーシャ: 22
HashMap の特徴
覚えておくべき最重要点は、HashMap のキーは常に一意であることです。既存のキーで新しい要素を put すると、古い値は新しい値で置き換えられます。
値は重複可能です。異なる複数のキーが同じ値を指してもかまいません。
もう 1 つの重要点は順序です。HashMap は追加順を気にしません。出力時にレコードが入れ替わって見えても、それが通常の挙動です。
4. TreeMap クラス: キー順ソート
HashMap と異なり、TreeMap はキーでソートされた順序で要素を保持します。
TreeMap を使うべきとき
要素がキーの昇順(または降順)に並んでいることが重要な場合です。例えば、電話帳をアルファベット順/五十音順に出力したいときなどです。
例:
import java.util.Map;
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
Map<String, String> phoneBook = new TreeMap<>();
phoneBook.put("ヴァーシャ", "+1-900-123-45-67");
phoneBook.put("マーシャ", "+1-900-555-55-55");
phoneBook.put("ペーチャ", "+1-900-222-33-44");
for (String name : phoneBook.keySet()) {
System.out.println(name + ": " + phoneBook.get(name));
}
}
}
出力:
マーシャ: +1-900-555-55-55
ペーチャ: +1-900-222-33-44
ヴァーシャ: +1-900-123-45-67
注意: キーは文字順にソートされています。
5. Map の基本操作
要素の追加と置換
Map<String, Integer> scores = new HashMap<>();
scores.put("アンナ", 90);
scores.put("イワン", 85);
scores.put("アンナ", 95); // 「アンナ」の値を上書き
値の取得
Integer annaScore = scores.get("アンナ"); // 95
Integer unknown = scores.get("ヴァーシャ"); // そのようなキーがない場合は null
キーや値の存在確認
scores.containsKey("イワン"); // true
scores.containsValue(85); // true
キーでエントリを削除
scores.remove("イワン");
サイズとクリア
int size = scores.size();
scores.clear(); // すべての要素を削除
5. Map の要素の走査
Map はリストではないのでインデックスはありません。ただし、次のように走査できます。
キーごと:
for (String key : scores.keySet()) {
System.out.println("キー: " + key + ", 値: " + scores.get(key));
}
値ごと:
for (Integer value : scores.values()) {
System.out.println("値: " + value);
}
キーと値のペアで(推奨):
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " => " + value);
}
HashMap と TreeMap の使い分けは?
HashMap は「デフォルト」の万能選手です。キーの順序が重要ではなく、操作速度が最優先であれば、ほとんどの場合これを選びます。
TreeMap は順序が必要なときに役立ちます。キーを自動的にソートして保持し、最小/最大キーの取得や範囲操作を素早く行えます。
結論: 9 割は HashMap を使い、データを常に「順序付き」で扱いたい場合は TreeMap を使いましょう。
6. Map の使用例
例 1: 電話帳
Map<String, String> phoneBook = new HashMap<>();
phoneBook.put("カーチャ", "+1-999-111-22-33");
phoneBook.put("オレグ", "+1-999-222-33-44");
phoneBook.put("カーチャ", "+1-999-555-66-77"); // カーチャの古い番号は新しい番号で置き換えられる
for (Map.Entry<String, String> entry : phoneBook.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
出力:
オレグ: +1-999-222-33-44
カーチャ: +1-999-555-66-77
例 2: 単語の出現回数のカウント
単語のリストがあり、各単語が何回出現したかを調べたいとします。
import java.util.*;
public class WordCount {
public static void main(String[] args) {
List<String> words = Arrays.asList("りんご", "バナナ", "りんご", "梨", "バナナ", "りんご");
Map<String, Integer> counts = new HashMap<>();
for (String word : words) {
int oldCount = counts.getOrDefault(word, 0); // キーがなければ 0
counts.put(word, oldCount + 1);
}
System.out.println(counts); // {梨=1, りんご=3, バナナ=2}
}
}
7. Map でよくあるミス
エラー No.1: キーと値の取り違え。 初心者はしばしばリストのようにインデックスで値を取得しようとしたり、キーが一意であることを忘れたりします。Map にインデックスはなく、あるのはキーだけです。
エラー No.2: null キーや null 値の使用。 HashMap では null キーが許容されますが、TreeMap では許容されません(NullPointerException が発生)。値はどちらも null にできますが、有用なことは稀です。
エラー No.3: HashMap に順序を期待する。 HashMap は一切の順序を保証しません。順序が必要なら LinkedHashMap(挿入順を保持)や TreeMap(キー順でソート)を使いましょう。
エラー No.4: 走査中に Map を変更する。 Map をループで走査しながら同時に要素を追加/削除すると、ConcurrentModificationException が発生することがあります。こうした場合は remove() を備えたイテレータを使うか、専用のコレクションを使用してください。
エラー No.5: == で比較して equals を使わない。 Map はキー(と値)の比較に equals を用います。独自のキー用クラスを作る場合は、必ず equals と hashCode をオーバーライドしましょう。
GO TO FULL VERSION