Java | 1 日 90 分 × 7 日アプリ学習 初級編:ミニToDoアプリ(CUI)

Web APP Java
スポンサーリンク

2日目のゴール

2日目のテーマは
「1日目で作ったミニToDoアプリを“設計として少し大人にする”こと です。

やることは大きく言うと三つです。

クラス設計を少し整理する(責務をはっきりさせる)
List 管理を「インデックス頼み」から一歩だけ進める
入力処理をもう少し丁寧にして、壊れにくくする

機能は変えません。
追加・一覧・削除のままです。
でも「中身の質」を上げていきます。


クラス設計を一段だけ整える

Task に「ID」と「完了フラグ」を足す

1日目の Task は「タイトルだけ」でした。
2日目では、次の2つを足します。

アプリ内で一意な「タスクID」
完了したかどうかのフラグ(boolean done)

まずはコードを見てください。

public class Task {
    int id;          // アプリ内で一意なID
    String title;    // タイトル
    boolean done;    // 完了したかどうか

    Task(int id, String title) {
        if (title == null || title.isEmpty()) {
            throw new IllegalArgumentException("タイトルは必須です。");
        }
        this.id = id;
        this.title = title;
        this.done = false;
    }

    void markDone() {
        this.done = true;
    }

    void show(int index) {
        String status = done ? "[x]" : "[ ]";
        System.out.println(index + ": " + status + " (id=" + id + ") " + title);
    }
}
Java

ここでの重要ポイントは三つです。

インデックスとは別に「タスクID」を持った
完了状態を boolean で持てるようにした
表示に「完了状態」と「ID」を含めた

まだ「完了機能」はメニューに出しませんが、
設計として「拡張できる形」にしておきます。

TaskManager に「次のID」を持たせる

Task に ID を持たせたので、
TaskManager が「ID を発行する役」になります。

import java.util.ArrayList;

public class TaskManager {
    ArrayList<Task> tasks;
    int nextId;

    TaskManager() {
        tasks = new ArrayList<>();
        nextId = 1;
    }

    void addTask(String title) {
        try {
            Task task = new Task(nextId, title);
            tasks.add(task);
            System.out.println("タスクを追加しました: id=" + task.id + " / " + title);
            nextId++;
        } catch (IllegalArgumentException e) {
            System.out.println("タスクの追加に失敗しました: " + e.getMessage());
        }
    }
    // 他のメソッドは後で
}
Java

ここでのポイントは、

ID の採番は TaskManager の責務
Task 自身は「渡されたIDを持つだけ」

という役割分担です。


List 管理を「インデックス頼み」から一歩進める

インデックス削除の弱点を知る

1日目の削除は「インデックス指定」でした。

一覧を表示する
0,1,2… の番号を見せる
その番号を入力してもらう

これはシンプルで良いのですが、
インデックスには弱点があります。

タスクを削除すると、後ろのインデックスが全部ずれる
「さっき 2番だったタスク」が、次の一覧では 1番になっている

小さいアプリなら問題ありませんが、
「安定した識別子」としては少し心もとない。

そこで今日は、

内部ではインデックスを使う
ユーザーには「ID」を見せる

という形にします。

削除は「インデックスで実行、IDで指定」

内部的には List を使っているので、
削除自体は remove(index) が一番楽です。

でも、ユーザーには「id=3 のタスクを消したい」と言わせたい。

なので、
「IDからインデックスを探す」メソッドを用意します。

int findIndexById(int id) {
    for (int i = 0; i < tasks.size(); i++) {
        if (tasks.get(i).id == id) {
            return i;
        }
    }
    return -1;
}
Java

そして削除メソッドをこう書き換えます。

void removeTaskById(int id) {
    int index = findIndexById(id);
    if (index == -1) {
        System.out.println("そのIDのタスクは存在しません: " + id);
        return;
    }
    Task removed = tasks.remove(index);
    System.out.println("タスクを削除しました: id=" + removed.id + " / " + removed.title);
}
Java

ここでの重要ポイントは、

List の削除はインデックスで行う
でも「どのインデックスか」は ID から計算する

という二段構えにしたことです。


入力処理を一段だけ丁寧にする

「数値入力」を共通化する

1日目では、削除番号のところだけ parseInt を書きました。
2日目では、「数値入力」を小さなメソッドに切り出します。

import java.util.Scanner;

public class InputHelper {
    Scanner scanner;

    InputHelper(Scanner scanner) {
        this.scanner = scanner;
    }

    String readLine(String prompt) {
        System.out.print(prompt);
        return scanner.nextLine();
    }

    Integer readInt(String prompt) {
        System.out.print(prompt);
        String line = scanner.nextLine();
        if (line == null || line.isEmpty()) {
            System.out.println("何も入力されていません。");
            return null;
        }
        try {
            return Integer.parseInt(line);
        } catch (NumberFormatException e) {
            System.out.println("整数で入力してください。");
            return null;
        }
    }
}
Java

ここでの狙いは、

「入力の細かい面倒」を Main から追い出す
Main は「何を聞きたいか」だけを書く

という状態にすることです。


2日目版 TaskManager 全体

import java.util.ArrayList;

public class TaskManager {
    ArrayList<Task> tasks;
    int nextId;

    TaskManager() {
        tasks = new ArrayList<>();
        nextId = 1;
    }

    void addTask(String title) {
        try {
            Task task = new Task(nextId, title);
            tasks.add(task);
            System.out.println("タスクを追加しました: id=" + task.id + " / " + title);
            nextId++;
        } catch (IllegalArgumentException e) {
            System.out.println("タスクの追加に失敗しました: " + e.getMessage());
        }
    }

    void showAll() {
        System.out.println("=== タスク一覧 ===");
        if (tasks.isEmpty()) {
            System.out.println("タスクはありません。");
            return;
        }
        for (int i = 0; i < tasks.size(); i++) {
            Task t = tasks.get(i);
            t.show(i);
        }
    }

    int findIndexById(int id) {
        for (int i = 0; i < tasks.size(); i++) {
            if (tasks.get(i).id == id) {
                return i;
            }
        }
        return -1;
    }

    void removeTaskById(int id) {
        int index = findIndexById(id);
        if (index == -1) {
            System.out.println("そのIDのタスクは存在しません: " + id);
            return;
        }
        Task removed = tasks.remove(index);
        System.out.println("タスクを削除しました: id=" + removed.id + " / " + removed.title);
    }
}
Java

2日目版 Main:少しだけ“読みやすく”

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        TaskManager manager = new TaskManager();
        Scanner scanner = new Scanner(System.in);
        InputHelper input = new InputHelper(scanner);

        while (true) {
            System.out.println();
            System.out.println("=== ミニToDoアプリ ===");
            System.out.println("1: タスク追加");
            System.out.println("2: タスク一覧");
            System.out.println("3: タスク削除(ID指定)");
            System.out.println("0: 終了");

            String choice = input.readLine("番号を選んでください: ");

            if ("0".equals(choice)) {
                System.out.println("終了します。");
                break;
            } else if ("1".equals(choice)) {
                String title = input.readLine("タスクのタイトルを入力してください: ");
                manager.addTask(title);
            } else if ("2".equals(choice)) {
                manager.showAll();
            } else if ("3".equals(choice)) {
                manager.showAll();
                Integer id = input.readInt("削除したいタスクのIDを入力してください: ");
                if (id != null) {
                    manager.removeTaskById(id);
                }
            } else {
                System.out.println("不正な入力です。もう一度選んでください。");
            }
        }

        scanner.close();
    }
}
Java

ここで注目してほしいのは、

Main が「何をしたいか」だけをしゃべっている
「どう入力を読むか」「どう List を触るか」は、別クラスに任せている

という読みやすさです。


2日目で絶対に押さえてほしい本質

今日いちばん大事なのは、
「同じ機能でも、設計を少し整えるだけで“アプリっぽさ”が一気に増す」
という感覚です。

Task に ID と done を持たせた
TaskManager が ID を採番し、List を管理する
削除は「ID指定 → インデックス検索 → remove」という流れにした
入力処理を InputHelper に切り出して、Main をスッキリさせた

機能は 1日目とほぼ同じです。
でも、コードの「息のしやすさ」が変わっているはずです。

もし余力があれば、
今日の設計のまま「完了フラグを切り替えるメニュー」を自分で足してみてください。

Task に markDone() はもうある
あとは「IDから Task を探して markDone する」メソッドを TaskManager に足すだけ

そこまでできたら、
あなたの ToDo アプリは、もう立派な「小さなプロダクト」です。

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