6日目のゴール
6日目のテーマは
「Map を“検索のインデックス”として意識して使えるようになること」 です。
ここまでであなたはすでに、
key で value を一発で引ける
value にオブジェクトを入れられる
Map と List を組み合わせてグループ分けできる
設定やスコアなど「今の状態」を Map で持てる
というところまで来ています。
6日目ではここから一歩進んで、
「たくさんのデータの中から、条件に合うものを素早く見つける」
そのために Map を「インデックス(索引)」として使う
という、現実のアプリでめちゃくちゃ重要な考え方を固めます。
題材は「商品カタログ+検索インデックス」です。
今日のイメージ:商品一覧と“検索の近道”
まずは“世界観”を言葉で決める
商品がたくさんあるとします。
商品には「商品ID」「名前」「カテゴリ」「価格」がある
画面には「全商品一覧」がある
ユーザーは「ID で商品を探したい」「カテゴリで絞り込みたい」
このとき、データの持ち方としてはこうなります。
全商品を 1 本の List で持つ
ID → 商品 という Map を用意する
カテゴリ → 商品のリスト という Map も用意する
つまり、
全データの「マスタ」
マスタに対する「検索の近道(インデックス)」
という構造です。
ここで Map が本領発揮します。
商品1件分をクラスで表現する
Product クラス
まずは、1件分の商品をクラスにします。
public class Product {
String id; // 商品ID(ユニーク)
String name; // 商品名
String category; // カテゴリ
int price; // 価格
Product(String id, String name, String category, int price) {
if (id == null || id.isEmpty()) {
throw new IllegalArgumentException("ID は必須です。");
}
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("名前は必須です。");
}
if (category == null || category.isEmpty()) {
throw new IllegalArgumentException("カテゴリは必須です。");
}
if (price < 0) {
throw new IllegalArgumentException("価格は 0 以上で指定してください。");
}
this.id = id;
this.name = name;
this.category = category;
this.price = price;
}
void show() {
System.out.println("ID: " + id + " / 名前: " + name + " / カテゴリ: " + category + " / 価格: " + price + "円");
}
}
Javaここまではおなじみです。
1件分をクラスにまとめる
ID はユニークという前提で扱う
show で自己紹介できるようにする
この「ID が一意」という前提が、
Map をインデックスとして使うときの土台になります。
マスタ+インデックスという構造を作る
Catalog クラスの役割
Catalog クラスには、こういう責務を持たせます。
全商品のマスタ一覧を持つ(List)
ID から商品を引くためのインデックス(Map)を持つ
カテゴリから商品一覧を引くためのインデックス(Map)を持つ
商品を追加したら、マスタとインデックスを両方更新する
これをコードにすると、こうなります。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class Catalog {
ArrayList<Product> products; // マスタ
Map<String, Product> byId; // ID → Product
Map<String, ArrayList<Product>> byCategory; // カテゴリ → Product のリスト
Catalog() {
products = new ArrayList<>();
byId = new HashMap<>();
byCategory = new HashMap<>();
}
}
Javaここでの重要ポイントは、
同じ Product 達を、
List と複数の Map で「いろんな角度から見られるようにしている」
という構造です。
商品を追加するときに“全部”を更新する
addProduct の考え方
やりたいことはこうです。
マスタの List に商品を追加する
ID インデックス(byId)に登録する
カテゴリインデックス(byCategory)にも登録する
これをコードにすると、こうなります。
void addProduct(Product p) {
if (p == null) {
System.out.println("null の商品は追加できません。");
return;
}
if (byId.containsKey(p.id)) {
System.out.println("そのIDはすでに使われています: " + p.id);
return;
}
products.add(p);
byId.put(p.id, p);
ArrayList<Product> list = byCategory.get(p.category);
if (list == null) {
list = new ArrayList<>();
byCategory.put(p.category, list);
}
list.add(p);
}
Javaここでやっていることを、丁寧に言葉にします。
ID が重複していないかチェックする
マスタの products に追加する
byId に「ID → Product」として登録する
byCategory から「そのカテゴリのリスト」を取り出す
なければ新しく作って Map に登録する
そのリストに商品を追加する
ここで超重要なのは、
同じ Product オブジェクトを、
List と複数の Map から「共有して見ている」
という点です。
Product は1つだけ
それを指す“参照”を、List と Map がそれぞれ持っている
というイメージです。
ID から一瞬で商品を引く
findById メソッド
ID から商品を探すのは、Map の得意技です。
Product findById(String id) {
if (id == null || id.isEmpty()) {
return null;
}
return byId.get(id);
}
JavaList だけで同じことをやろうとすると、
全件をループして p.id.equals(id) をチェックする
という処理が必要になります。
Map をインデックスとして持っておけば、get(id) 1発で済みます。
ここで改めて感じてほしいのは、
Map は「検索のショートカット」
List は「全体を順番に眺めるためのマスタ」
という役割分担です。
カテゴリで絞り込んで表示する
showByCategory メソッド
カテゴリから商品一覧を引くのも、Map の得意技です。
void showByCategory(String category) {
System.out.println("=== カテゴリ: " + category + " の商品 ===");
ArrayList<Product> list = byCategory.get(category);
if (list == null || list.isEmpty()) {
System.out.println("このカテゴリの商品はありません。");
return;
}
for (Product p : list) {
p.show();
}
}
Javaここでやっているのは、
byCategory から「そのカテゴリのリスト」を取り出す
なければ「ありません」と表示
あれば、そのリストを普通の List としてループ
という流れです。
Map は「どの List を見るか」を決めるインデックス
List は「そのグループの中身」を表す
という構造が、ここでもそのまま出ています。
全商品を一覧表示する
showAll メソッド
マスタをそのまま眺めるメソッドも用意しておきます。
void showAll() {
System.out.println("=== 全商品一覧 ===");
if (products.isEmpty()) {
System.out.println("まだ商品が登録されていません。");
return;
}
for (Product p : products) {
p.show();
}
}
Javaここでは Map は使っていません。
「全部を順番に見る」だけなら、
マスタの List をそのまま回すのが一番素直です。
商品を削除するときの“インデックスの更新”
removeById の考え方
削除は少しだけ慎重さが必要です。
やりたいことはこうです。
ID から Product を見つける
マスタの List からその Product を削除する
byId からも削除する
byCategory の該当カテゴリのリストからも削除する
そのカテゴリのリストが空になったら、カテゴリ自体を Map から消す
コードにすると、こうなります。
boolean removeById(String id) {
Product p = byId.get(id);
if (p == null) {
System.out.println("そのIDの商品は存在しません: " + id);
return false;
}
products.remove(p);
byId.remove(id);
ArrayList<Product> list = byCategory.get(p.category);
if (list != null) {
list.remove(p);
if (list.isEmpty()) {
byCategory.remove(p.category);
}
}
System.out.println("削除しました: " + id + " / " + p.name);
return true;
}
Javaここでの超重要ポイントは、
マスタと全てのインデックスを「一緒に更新する」
ということです。
マスタだけ消して Map を放置すると、
「存在しない商品を指すインデックス」が残ってしまいます。
逆に、Map だけ消してマスタを放置しても、
「一覧には出るのに検索できない商品」が生まれます。
「マスタとインデックスはセットで更新する」
これは現実のアプリでも、すごく大事な感覚です。
Main から見た「カタログ+インデックス」
全体の動き
public class Main {
public static void main(String[] args) {
Catalog catalog = new Catalog();
catalog.addProduct(new Product("P001", "Java入門", "本", 3000));
catalog.addProduct(new Product("P002", "アルゴリズム図鑑", "本", 2500));
catalog.addProduct(new Product("P003", "ワイヤレスマウス", "ガジェット", 1500));
catalog.addProduct(new Product("P004", "メカニカルキーボード", "ガジェット", 8000));
System.out.println("=== 全商品 ===");
catalog.showAll();
System.out.println();
System.out.println("ID で検索: P003");
Product p = catalog.findById("P003");
if (p != null) {
p.show();
}
System.out.println();
catalog.showByCategory("本");
System.out.println();
catalog.showByCategory("ガジェット");
System.out.println();
System.out.println("商品を削除: P002");
catalog.removeById("P002");
System.out.println();
catalog.showAll();
System.out.println();
catalog.showByCategory("本");
}
}
Javaこの Main を眺めてみてください。
商品を追加する
全商品を一覧表示する
ID で商品を検索する
カテゴリで絞り込む
商品を削除する
Main からは、
List や Map の存在はほとんど見えません。
見えているのは、
商品
カタログ
ID 検索
カテゴリ絞り込み
という「アプリの世界」です。
裏側では、
ArrayList<Product> がマスタを
Map<String, Product> が ID インデックスを
Map<String, ArrayList<Product>> がカテゴリインデックスを
それぞれ key/value の形で支えています。
6日目で絶対に押さえてほしい本質
今日いちばん大事なのは、
「Map は“検索のインデックス”として使うと真価を発揮する」
という感覚です。
マスタ(全部入りの List)
インデックス(Map)
この二階建て構造を、頭の中にしっかり置いておいてください。
ID → 1件を一発で引くインデックス
カテゴリ → 複数件をまとめて引くインデックス
インデックスは「同じオブジェクトへの別ルート」
そしてもうひとつ。
マスタとインデックスは、
追加・更新・削除のたびに「一緒に更新する」
この感覚が入っていれば、
現実のアプリで出てくる「検索」「絞り込み」「一覧表示」が、
かなりクリアに設計できるようになります。
7日目では、ここまでの Map の使い方をまとめて、
「アプリ全体の中で Map をどう配置するか」を整理していきます。


