4日目のゴール
4日目のテーマは
「Map と List を組み合わせて、“現実っぽいデータ”を整理して扱えるようになること」 です。
ここまでであなたはすでに、
key は「名前・ID・ラベル」
value には数値やオブジェクトを入れられるput / get / containsKey / remove / keySet で基本操作ができる
というところまで来ています。
4日目ではここから一歩進んで、
「カテゴリ → そのカテゴリに属するものの一覧」という構造
Map と List を組み合わせた Map<String, List<何か>> という形
「1つのデータを、Map を使って“グループ分け”して見る」
という、アプリでめちゃくちゃよく使うパターンを身につけます。
題材は「ジャンル別の本棚アプリ」です。
今日のイメージ:ジャンルごとの本棚
まずは“世界観”を言葉で決める
本がたくさんあるとします。
本には「タイトル」と「ジャンル」がある
ジャンルは「SF」「ビジネス」「小説」など
アプリでは「ジャンルごとに本を一覧表示したい」
このとき、欲しい構造はこうです。
「SF」 → SF の本の一覧
「ビジネス」 → ビジネス書の一覧
「小説」 → 小説の一覧
つまり、
「ジャンル(key) → そのジャンルに属する本のリスト(value)」
という形になります。
これを Java で表現すると、
Map<String, ArrayList<Book>>
という形になります。
ここが今日の一番大事なポイントです。
1冊分の本をクラスで表現する
Book クラス
まずは、1冊分の本を表すクラスを作ります。
public class Book {
String title;
String genre;
Book(String title, String genre) {
if (title == null || title.isEmpty()) {
throw new IllegalArgumentException("タイトルは必須です。");
}
if (genre == null || genre.isEmpty()) {
throw new IllegalArgumentException("ジャンルは必須です。");
}
this.title = title;
this.genre = genre;
}
void show() {
System.out.println("タイトル: " + title + " / ジャンル: " + genre);
}
}
Javaここまではおなじみですね。
「1件分の情報をひとまとまりにする」
「コンストラクタで変な値を防ぐ」
「show で自己紹介できるようにする」
このパターンは、もう体に入ってきているはずです。
Map<String, List<Book>> という形を理解する
「ジャンル → 本のリスト」の対応表
次に、本棚全体を管理するクラスを作ります。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class BookShelf {
Map<String, ArrayList<Book>> shelves;
BookShelf() {
shelves = new HashMap<>();
}
}
JavaMap<String, ArrayList<Book>> は、
key:ジャンル名(String)
value:そのジャンルの本のリスト(ArrayList<Book>)
という意味です。
ここでの重要ポイントは、
「Map の value に、さらに List(可変長の集まり)を入れている」
という構造です。
「ジャンルごとに本が何冊あるか分からない」
→ だから value は可変長の List がぴったり
という発想です。
本をジャンルごとの棚に追加する
addBook メソッドの考え方
やりたいことはこうです。
本を1冊受け取る
その本の genre を見る
その genre の棚(リスト)がなければ新しく作る
その棚に本を追加する
これをコードにすると、こうなります。
void addBook(Book book) {
if (book == null) {
System.out.println("null の本は追加できません。");
return;
}
String genre = book.genre;
ArrayList<Book> list = shelves.get(genre);
if (list == null) {
list = new ArrayList<>();
shelves.put(genre, list);
}
list.add(book);
}
Javaここでやっていることを、丁寧に言葉にします。
shelves.get(genre) で「そのジャンルの本リスト」を取り出そうとする
まだ一度もそのジャンルが登録されていなければ null が返るnull の場合は「そのジャンル用の新しいリスト」を作る
作ったリストを put(genre, list) で Map に登録する
最後に、そのリストに add(book) で本を追加する
ここでの超重要ポイントは、
「Map に入っている List を取り出して、その List に add している」
という構造です。
put で「List を丸ごと入れ直している」のではなく、
一度取り出した List に対して add しているだけです。
特定のジャンルの本だけを表示する
showByGenre メソッド
次に、「SF だけ見たい」「ビジネスだけ見たい」という表示を作ります。
void showByGenre(String genre) {
System.out.println("=== ジャンル: " + genre + " の本 ===");
ArrayList<Book> list = shelves.get(genre);
if (list == null || list.isEmpty()) {
System.out.println("このジャンルの本はありません。");
return;
}
for (Book b : list) {
b.show();
}
}
Javaここでやっているのはシンプルです。
shelves.get(genre) で、そのジャンルの本リストを取り出す
なければ「ありません」と表示して終わる
あれば、そのリストを普通の List と同じようにループで回す
ここで感じてほしいのは、
「Map は“どの List を見るか”を決めるためのインデックス」
として働いている、ということです。
List の中身を見始めたら、
あとは「いつもの ArrayList の世界」です。
全ジャンルをまとめて表示する
shelves 全体をなめる
「本棚全体を眺めたい」というときは、
Map の中身を全部なめます。
void showAll() {
System.out.println("=== 全ての本棚 ===");
if (shelves.isEmpty()) {
System.out.println("まだ本が登録されていません。");
return;
}
for (String genre : shelves.keySet()) {
System.out.println("■ ジャンル: " + genre);
ArrayList<Book> list = shelves.get(genre);
for (Book b : list) {
System.out.print(" - ");
b.show();
}
}
}
Javaここでの流れはこうです。
keySet() で「全てのジャンル名」を取り出す
ジャンルごとに、そのジャンルの本リストを get(genre) で取り出す
そのリストをループで回して表示する
つまり、
外側のループ:Map(ジャンルごとの棚)
内側のループ:List(その棚の中の本)
という二重構造になっています。
Main から見た「ジャンル別本棚アプリ」
全体の動き
public class Main {
public static void main(String[] args) {
BookShelf shelf = new BookShelf();
shelf.addBook(new Book("Java入門", "プログラミング"));
shelf.addBook(new Book("Effective Java", "プログラミング"));
shelf.addBook(new Book("7つの習慣", "ビジネス"));
shelf.addBook(new Book("人を動かす", "ビジネス"));
shelf.addBook(new Book("星の王子さま", "小説"));
System.out.println("=== 全体 ===");
shelf.showAll();
System.out.println();
shelf.showByGenre("プログラミング");
System.out.println();
shelf.showByGenre("ビジネス");
System.out.println();
shelf.showByGenre("SF"); // 登録されていないジャンル
}
}
Javaこの Main を眺めてみてください。
本棚を作る
本を追加する(ジャンルごとの棚に自動で振り分けられる)
全体を表示する
特定のジャンルだけ表示する
Main からは、
Map とか ArrayList という言葉は一切見えません。
見えているのは、
本
ジャンル
本棚
という「アプリの世界」だけです。
裏側では、
Map<String, ArrayList<Book>> が
「ジャンル → そのジャンルの本のリスト」という
key/value の世界を支えています。
もう一歩:本を削除する
特定のジャンルから本を削除する
少しだけ踏み込んで、「削除」もやってみます。
「プログラミングジャンルから ‘Java入門’ を消したい」
というケースです。
boolean removeBook(String genre, String title) {
ArrayList<Book> list = shelves.get(genre);
if (list == null) {
System.out.println("そのジャンルは存在しません: " + genre);
return false;
}
for (int i = 0; i < list.size(); i++) {
Book b = list.get(i);
if (b.title.equals(title)) {
list.remove(i);
System.out.println("削除しました: " + title + " / ジャンル: " + genre);
if (list.isEmpty()) {
shelves.remove(genre);
System.out.println("ジャンル " + genre + " の本がなくなったので棚を削除しました。");
}
return true;
}
}
System.out.println("そのタイトルの本は見つかりません: " + title);
return false;
}
Javaここでのポイントは3つです。
ジャンルの棚(List)を Map から取り出す
List の中をインデックス付きで回して、タイトル一致で削除する
削除後、そのジャンルの本が 0 冊になったら、Map からそのジャンル自体を remove する
ここでもやはり、
「Map は“どの List を触るか”を決めるためのインデックス」
「実際の削除は List の世界で行う」
という役割分担になっています。
4日目で絶対に押さえてほしい本質
今日いちばん大事なのは、
「Map と List を組み合わせると、“グループ分けされた可変長データ”を自然に表現できる」
という感覚です。
ジャンル → 本のリスト
カテゴリ → 商品のリスト
タグ → 記事のリスト
ユーザーID → その人の注文履歴のリスト
どれも形は同じです。
Map<String, ArrayList<何か>>
そして役割はこうです。
Map は「どのグループ(どの List)を見るか」を決めるインデックス
List は「そのグループの中身(可変長の集まり)」を表す
この「二階建て構造」が見えるようになると、
アプリの設計の幅が一気に広がります。
もし余力があれば、今日の BookShelf に
ジャンルごとの冊数を表示する
全冊数を表示する
特定のタイトルを、ジャンルをまたいで検索する
などを自分で足してみてください。
そのとき必ず、
「どの Map を、どの List を、どんな条件でなめるか」
を考えることになります。
そこまで考えられたら、
あなたはもう「Map を知っている人」ではなく、
「Map と List で“世界の構造”を組み立てられる人」 になっています。


