Java | 1 日 90 分 × 7 日アプリ学習 初級編:Map を使うアプリ

Web APP Java
スポンサーリンク

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<>();
    }
}
Java

Map<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 で“世界の構造”を組み立てられる人」 になっています。

タイトルとURLをコピーしました