2日目のゴール
2日目のテーマは
「Map を“ただの辞書”から、“更新・削除・一覧表示ができるデータ構造”として扱えるようになること」 です。
1日目であなたはすでに、
キーは「名前・ラベル・ID」
値は「本当に知りたい中身」put(key, value) で登録get(key) で取り出し
登録されていないキーは null
ここまではつかめています。
2日目ではここから一歩進んで、
同じキーに再度 put したらどうなるか
キーが存在するかどうかを調べる
キーと値を「全部なめる」
削除する
という、「Map をちゃんと“管理する”」感覚を身につけます。
題材は、小さな「ログイン回数カウンター」です。
同じ key に put すると“上書き”になる
1日目の復習を少しだけ深掘り
まずは、次のコードを見てください。
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> phoneBook = new HashMap<>();
phoneBook.put("山田太郎", "090-1111-2222");
phoneBook.put("山田太郎", "080-5555-6666");
String yamada = phoneBook.get("山田太郎");
System.out.println("山田太郎の電話番号: " + yamada);
}
}
Java出力は "080-5555-6666" になります。
理由はシンプルで、
同じキーに対する2回目の put が、
1回目の値を「上書き」するからです。
ここで押さえておきたいのは、
Map の中では、
「キーは一意(ユニーク)で、最後に登録した値が有効」
というルールがある、ということです。
この性質は、「設定」「最新状態」「カウンター」などを扱うときに、とても役立ちます。
Map を「カウンター」として使う
ログイン回数を数えるイメージ
次のような状況を考えてみます。
“alice” がログインした
“bob” がログインした
もう一度 “alice” がログインした
このとき、
“alice” → 2回
“bob” → 1回
という情報を持っておきたい。
ここで Map が使えます。
キー:ユーザー名(String)
値:ログイン回数(Integer)
という形にすればいい。
基本形のコード
import java.util.HashMap;
import java.util.Map;
public class LoginCounter {
public static void main(String[] args) {
Map<String, Integer> loginCount = new HashMap<>();
countLogin(loginCount, "alice");
countLogin(loginCount, "bob");
countLogin(loginCount, "alice");
System.out.println("alice のログイン回数: " + loginCount.get("alice"));
System.out.println("bob のログイン回数: " + loginCount.get("bob"));
}
static void countLogin(Map<String, Integer> map, String userName) {
Integer current = map.get(userName);
if (current == null) {
map.put(userName, 1);
} else {
map.put(userName, current + 1);
}
}
}
Javaここでやっていることを、丁寧に言葉にします。
Map<String, Integer> は「ユーザー名 → 回数」の対応表get(userName) で「今何回ログインしているか」を取り出す
まだ一度もログインしていないユーザーは null になるnull なら「初回ログイン」とみなして 1 を入れる
そうでなければ「今の回数 + 1」で上書きする
ここで重要なのは、
「Map は“現在の状態”を持つのに向いている」
という感覚です。
put は「追加」ではなく、「そのキーの“今の値”を更新する」イメージで捉えると、しっくりきます。
key が存在するかどうかを調べる
containsKey の意味
get で null が返ってきたとき、
「本当に登録されていない」のか
「値として null が入っている」のか
が区別できません。
値に null を入れない設計なら問題になりにくいですが、
Map にはそもそも「キーが存在するかどうか」を調べるメソッドがあります。
boolean exists = map.containsKey("alice");
JavacontainsKey(key) は、
そのキーが登録されていれば true
登録されていなければ false
を返します。
get と containsKey を組み合わせる
さっきのカウンターを、containsKey を使って書き直してみます。
static void countLogin(Map<String, Integer> map, String userName) {
if (!map.containsKey(userName)) {
map.put(userName, 1);
} else {
int current = map.get(userName);
map.put(userName, current + 1);
}
}
Javaやっていることは同じですが、
「キーが存在するかどうか」を明示的にチェックしている
という点で、意図がよりはっきりします。
Map の中身を「全部なめる」
keySet() でキーの一覧を取る
Map は、List と違って「0番目」「1番目」という概念がありません。
その代わりに、
「登録されているキーの一覧」
「登録されている値の一覧」
「キーと値のペアの一覧」
を取り出すメソッドが用意されています。
まずはキーの一覧。
for (String user : loginCount.keySet()) {
Integer count = loginCount.get(user);
System.out.println(user + " : " + count);
}
JavakeySet() は、
「Map に登録されている全てのキーの集合」
を返します。
それを拡張 for 文で回して、get(key) で値を取り出す、というパターンです。
例:ログイン回数を全部表示する
さっきの LoginCounter に、一覧表示を足してみます。
static void showAll(Map<String, Integer> map) {
System.out.println("=== ログイン回数一覧 ===");
for (String user : map.keySet()) {
Integer count = map.get(user);
System.out.println(user + " : " + count);
}
}
JavaMain からはこう呼べます。
showAll(loginCount);
Javaここで感じてほしいのは、
List のときは「インデックスで回す」
Map のときは「キーの一覧で回す」
という違いです。
key / value のペアを直接扱う
entrySet() のイメージ
もう一歩進んで、
「キーと値のペア」を直接扱う方法もあります。
for (Map.Entry<String, Integer> entry : loginCount.entrySet()) {
String user = entry.getKey();
Integer count = entry.getValue();
System.out.println(user + " : " + count);
}
JavaentrySet() は、
「キーと値のペア(Entry)の集合」
を返します。
Entry は、
getKey() でキーgetValue() で値
を取り出せる、小さなオブジェクトです。
今日の段階では、
「キー一覧で回す方法」と
「Entry で回す方法」がある
と知っておけば十分です。
Map から削除する
remove(key) の基本
Map から「このキーのデータはいらない」と消したいときは、remove を使います。
loginCount.remove("bob");
Javaこれで、
“bob” → 1
という対応関係が、Map から消えます。
remove したあとに get("bob") すると、null が返ってきます。
例:一定回数以下のユーザーを消す
少しだけ応用してみます。
「ログイン回数が 1 回だけのユーザーは消す」
という処理を書いてみましょう。
static void removeLowUsers(Map<String, Integer> map) {
// 削除を伴うので、まずキーをコピーする
java.util.List<String> keys = new java.util.ArrayList<>(map.keySet());
for (String user : keys) {
int count = map.get(user);
if (count <= 1) {
map.remove(user);
}
}
}
Javaここで大事なのは、
Map を回しながら直接 remove すると危険なので、
一度キーの一覧を別のリストにコピーしてから回している
という点です。
「削除を伴うループは、対象をコピーしてから回す」
というのは、Map でも List でも役立つ考え方です。
2日目のミニアプリ完成形
LoginCounter 全体
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
public class LoginCounter {
public static void main(String[] args) {
Map<String, Integer> loginCount = new HashMap<>();
countLogin(loginCount, "alice");
countLogin(loginCount, "bob");
countLogin(loginCount, "alice");
countLogin(loginCount, "charlie");
showAll(loginCount);
System.out.println("1回しかログインしていないユーザーを削除します。");
removeLowUsers(loginCount);
showAll(loginCount);
}
static void countLogin(Map<String, Integer> map, String userName) {
Integer current = map.get(userName);
if (current == null) {
map.put(userName, 1);
} else {
map.put(userName, current + 1);
}
}
static void showAll(Map<String, Integer> map) {
System.out.println("=== ログイン回数一覧 ===");
for (String user : map.keySet()) {
Integer count = map.get(user);
System.out.println(user + " : " + count);
}
}
static void removeLowUsers(Map<String, Integer> map) {
ArrayList<String> keys = new ArrayList<>(map.keySet());
for (String user : keys) {
int count = map.get(user);
if (count <= 1) {
map.remove(user);
}
}
}
}
Javaこのコードを眺めてみてください。
ユーザー名 → ログイン回数
という「key / value」の関係が、
Map の中にきれいに収まっています。
put で「今の状態」を更新し、get で取り出し、keySet で一覧を回し、remove でいらないものを消す。
これが、2日目で押さえておきたい「Map の基本操作フルセット」です。
2日目で絶対に押さえてほしい本質
今日いちばん大事なのは、
「Map は“キーから今の状態を一発で引ける表”だ」
という感覚を持てたかどうかです。
同じキーに put すると「上書き」になるget で取り出し、null なら「登録されていない」と判断するcontainsKey で「キーの存在」を明示的にチェックできるkeySet や entrySet で「全部なめる」ことができるremove で「このキーのデータはいらない」と消せる
このあたりが、自然に口から説明できるようになっていれば、
2日目としてはかなりいい仕上がりです。
次のステップでは、
値にオブジェクトを入れる(Map<String, User>)
Map と List を組み合わせる
「設定」「キャッシュ」「インデックス」としての Map
といった、より“アプリっぽい”使い方に踏み込んでいきます。


