10日で覚えるJavaDay 10: 総合プロジェクト
books.chapter 1010日で覚えるJava

Day 10: 総合プロジェクト

今日のゴール

これまで学んだすべてのJava技術を使って、コンソールベースのタスク管理アプリケーションを構築します。


プロジェクト概要

項目 内容
アプリ名 TaskManager
機能 タスクのCRUD、ファイル保存、検索・フィルタ
使用技術 クラス、継承、コレクション、ファイルI/O、Stream API
flowchart TB
    subgraph App["TaskManager"]
        UI["メインメニュー<br>ユーザー入力"]
        Service["TaskService<br>ビジネスロジック"]
        Storage["FileStorage<br>ファイルI/O"]
        Model["Task / Priority<br>データモデル"]
    end
    UI --> Service --> Storage
    Service --> Model
    style UI fill:#3b82f6,color:#fff
    style Service fill:#22c55e,color:#fff
    style Storage fill:#f59e0b,color:#fff
    style Model fill:#8b5cf6,color:#fff

Step 1: データモデル

Priority(列挙型)

enum Priority {
    HIGH("高"), MEDIUM("中"), LOW("低");

    private final String label;

    Priority(String label) {
        this.label = label;
    }

    String getLabel() { return label; }
}

Task(レコード → クラスに拡張)

import java.time.LocalDate;

class Task {
    private static int nextId = 1;

    private final int id;
    private String title;
    private String description;
    private Priority priority;
    private boolean completed;
    private final LocalDate createdDate;

    Task(String title, String description, Priority priority) {
        this.id = nextId++;
        this.title = title;
        this.description = description;
        this.priority = priority;
        this.completed = false;
        this.createdDate = LocalDate.now();
    }

    // ファイルから復元用
    Task(int id, String title, String description,
         Priority priority, boolean completed, LocalDate createdDate) {
        this.id = id;
        this.title = title;
        this.description = description;
        this.priority = priority;
        this.completed = completed;
        this.createdDate = createdDate;
        if (id >= nextId) {
            nextId = id + 1;
        }
    }

    // getter
    int getId() { return id; }
    String getTitle() { return title; }
    String getDescription() { return description; }
    Priority getPriority() { return priority; }
    boolean isCompleted() { return completed; }
    LocalDate getCreatedDate() { return createdDate; }

    // setter
    void setTitle(String title) { this.title = title; }
    void setDescription(String description) { this.description = description; }
    void setPriority(Priority priority) { this.priority = priority; }
    void setCompleted(boolean completed) { this.completed = completed; }

    // CSV変換
    String toCsv() {
        return String.join(",",
            String.valueOf(id),
            title,
            description,
            priority.name(),
            String.valueOf(completed),
            createdDate.toString()
        );
    }

    static Task fromCsv(String csv) {
        String[] parts = csv.split(",", 6);
        return new Task(
            Integer.parseInt(parts[0]),
            parts[1],
            parts[2],
            Priority.valueOf(parts[3]),
            Boolean.parseBoolean(parts[4]),
            LocalDate.parse(parts[5])
        );
    }

    @Override
    public String toString() {
        String status = completed ? "✓" : "○";
        return String.format("[%s] #%d %s [%s] - %s (%s)",
            status, id, title, priority.getLabel(), description, createdDate);
    }
}

Step 2: ファイル保存

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

class FileStorage {
    private final Path filePath;

    FileStorage(String filename) {
        this.filePath = Path.of(filename);
    }

    List<Task> load() {
        List<Task> tasks = new ArrayList<>();
        try {
            if (Files.exists(filePath)) {
                List<String> lines = Files.readAllLines(filePath);
                for (String line : lines) {
                    if (!line.isBlank()) {
                        tasks.add(Task.fromCsv(line));
                    }
                }
            }
        } catch (IOException e) {
            System.err.println("ファイル読み込みエラー: " + e.getMessage());
        }
        return tasks;
    }

    void save(List<Task> tasks) {
        try {
            List<String> lines = tasks.stream()
                .map(Task::toCsv)
                .toList();
            Files.write(filePath, lines);
        } catch (IOException e) {
            System.err.println("ファイル書き込みエラー: " + e.getMessage());
        }
    }
}

Step 3: ビジネスロジック

import java.util.*;
import java.util.stream.Collectors;

class TaskService {
    private final List<Task> tasks;
    private final FileStorage storage;

    TaskService(FileStorage storage) {
        this.storage = storage;
        this.tasks = new ArrayList<>(storage.load());
    }

    // タスク追加
    Task addTask(String title, String description, Priority priority) {
        Task task = new Task(title, description, priority);
        tasks.add(task);
        storage.save(tasks);
        return task;
    }

    // タスク完了
    boolean completeTask(int id) {
        return findById(id).map(task -> {
            task.setCompleted(true);
            storage.save(tasks);
            return true;
        }).orElse(false);
    }

    // タスク削除
    boolean deleteTask(int id) {
        boolean removed = tasks.removeIf(t -> t.getId() == id);
        if (removed) {
            storage.save(tasks);
        }
        return removed;
    }

    // 全タスク取得
    List<Task> getAllTasks() {
        return Collections.unmodifiableList(tasks);
    }

    // 未完了タスク
    List<Task> getPendingTasks() {
        return tasks.stream()
            .filter(t -> !t.isCompleted())
            .toList();
    }

    // 優先度でフィルタ
    List<Task> getTasksByPriority(Priority priority) {
        return tasks.stream()
            .filter(t -> t.getPriority() == priority)
            .toList();
    }

    // キーワード検索
    List<Task> search(String keyword) {
        String lower = keyword.toLowerCase();
        return tasks.stream()
            .filter(t -> t.getTitle().toLowerCase().contains(lower)
                      || t.getDescription().toLowerCase().contains(lower))
            .toList();
    }

    // 統計情報
    Map<String, Object> getStatistics() {
        Map<String, Object> stats = new LinkedHashMap<>();
        stats.put("総タスク数", tasks.size());
        stats.put("完了", tasks.stream().filter(Task::isCompleted).count());
        stats.put("未完了", tasks.stream().filter(t -> !t.isCompleted()).count());

        Map<Priority, Long> byPriority = tasks.stream()
            .collect(Collectors.groupingBy(Task::getPriority, Collectors.counting()));
        stats.put("優先度別", byPriority);

        return stats;
    }

    // ID検索
    private Optional<Task> findById(int id) {
        return tasks.stream()
            .filter(t -> t.getId() == id)
            .findFirst();
    }
}

Step 4: メインメニュー

import java.util.List;
import java.util.Scanner;

public class TaskManager {
    private final TaskService service;
    private final Scanner scanner;

    TaskManager() {
        FileStorage storage = new FileStorage("tasks.csv");
        this.service = new TaskService(storage);
        this.scanner = new Scanner(System.in);
    }

    void run() {
        System.out.println("=== タスク管理アプリ ===");

        while (true) {
            showMenu();
            String choice = scanner.nextLine().trim();

            switch (choice) {
                case "1" -> addTask();
                case "2" -> listTasks(service.getAllTasks());
                case "3" -> listTasks(service.getPendingTasks());
                case "4" -> completeTask();
                case "5" -> deleteTask();
                case "6" -> searchTasks();
                case "7" -> filterByPriority();
                case "8" -> showStatistics();
                case "0" -> {
                    System.out.println("終了します。");
                    return;
                }
                default -> System.out.println("無効な選択です。");
            }
            System.out.println();
        }
    }

    private void showMenu() {
        System.out.println("""
            --- メニュー ---
            1. タスク追加
            2. 全タスク表示
            3. 未完了タスク表示
            4. タスク完了
            5. タスク削除
            6. キーワード検索
            7. 優先度フィルタ
            8. 統計情報
            0. 終了
            """);
        System.out.print("選択: ");
    }

    private void addTask() {
        System.out.print("タイトル: ");
        String title = scanner.nextLine();

        System.out.print("説明: ");
        String description = scanner.nextLine();

        System.out.print("優先度 (1:高 2:中 3:低): ");
        Priority priority = switch (scanner.nextLine().trim()) {
            case "1" -> Priority.HIGH;
            case "3" -> Priority.LOW;
            default -> Priority.MEDIUM;
        };

        Task task = service.addTask(title, description, priority);
        System.out.println("追加しました: " + task);
    }

    private void listTasks(List<Task> tasks) {
        if (tasks.isEmpty()) {
            System.out.println("タスクがありません。");
            return;
        }
        tasks.forEach(System.out::println);
        System.out.printf("(%d件)%n", tasks.size());
    }

    private void completeTask() {
        System.out.print("完了するタスクID: ");
        try {
            int id = Integer.parseInt(scanner.nextLine().trim());
            if (service.completeTask(id)) {
                System.out.println("タスク #" + id + " を完了しました。");
            } else {
                System.out.println("タスクが見つかりません。");
            }
        } catch (NumberFormatException e) {
            System.out.println("無効なIDです。");
        }
    }

    private void deleteTask() {
        System.out.print("削除するタスクID: ");
        try {
            int id = Integer.parseInt(scanner.nextLine().trim());
            if (service.deleteTask(id)) {
                System.out.println("タスク #" + id + " を削除しました。");
            } else {
                System.out.println("タスクが見つかりません。");
            }
        } catch (NumberFormatException e) {
            System.out.println("無効なIDです。");
        }
    }

    private void searchTasks() {
        System.out.print("キーワード: ");
        String keyword = scanner.nextLine();
        listTasks(service.search(keyword));
    }

    private void filterByPriority() {
        System.out.print("優先度 (1:高 2:中 3:低): ");
        Priority priority = switch (scanner.nextLine().trim()) {
            case "1" -> Priority.HIGH;
            case "3" -> Priority.LOW;
            default -> Priority.MEDIUM;
        };
        listTasks(service.getTasksByPriority(priority));
    }

    private void showStatistics() {
        var stats = service.getStatistics();
        System.out.println("=== 統計情報 ===");
        stats.forEach((key, value) ->
            System.out.printf("%s: %s%n", key, value));
    }

    public static void main(String[] args) {
        new TaskManager().run();
    }
}

10日間の振り返り

flowchart TB
    D1["Day 1<br>Javaの基礎<br>Hello World"]
    D2["Day 2<br>変数とデータ型"]
    D3["Day 3<br>制御構文"]
    D4["Day 4<br>配列と文字列"]
    D5["Day 5<br>メソッド"]
    D6["Day 6<br>クラスと<br>オブジェクト"]
    D7["Day 7<br>継承と<br>ポリモーフィズム"]
    D8["Day 8<br>例外処理と<br>ファイルI/O"]
    D9["Day 9<br>コレクションと<br>ジェネリクス"]
    D10["Day 10<br>総合プロジェクト"]
    D1 --> D2 --> D3 --> D4 --> D5
    D5 --> D6 --> D7 --> D8 --> D9 --> D10
    style D1 fill:#3b82f6,color:#fff
    style D2 fill:#3b82f6,color:#fff
    style D3 fill:#3b82f6,color:#fff
    style D4 fill:#3b82f6,color:#fff
    style D5 fill:#3b82f6,color:#fff
    style D6 fill:#22c55e,color:#fff
    style D7 fill:#22c55e,color:#fff
    style D8 fill:#f59e0b,color:#fff
    style D9 fill:#f59e0b,color:#fff
    style D10 fill:#8b5cf6,color:#fff
Day トピック 使用した概念
1 Javaの世界へようこそ JDK, JVM, main, println
2 変数とデータ型 プリミティブ型, String, 演算子
3 制御構文 if, switch, for, while
4 配列と文字列 配列, String, StringBuilder
5 メソッド 引数, 戻り値, オーバーロード, 再帰
6 クラスとオブジェクト コンストラクタ, カプセル化, static, record
7 継承とポリモーフィズム extends, interface, abstract, sealed
8 例外処理とファイルI/O try-catch, Files, Path
9 コレクションとジェネリクス List, Map, Set, Stream API
10 総合プロジェクト 全技術の統合

次のステップ

Javaの基礎を習得しました。次に学ぶべきトピックを紹介します。

トピック 説明
ラムダ式と関数型インターフェース Stream APIをより深く理解
マルチスレッド 並列処理、ExecutorService
データベース JDBC、JPA
Webフレームワーク Spring Boot
ビルドツール Maven、Gradle
テスト JUnit 5、Mockito
デザインパターン GoF パターン

練習問題

問題1: 機能追加

タスクの編集機能(タイトル・説明・優先度の変更)を追加してください。

問題2: 応用

タスクに期限日(dueDate)を追加し、期限切れタスクの一覧表示機能を実装してください。

チャレンジ問題

CSVの代わりにJSON形式でタスクを保存するように変更してください。外部ライブラリを使わずに、自分でJSON文字列の生成とパースを実装してみましょう。


参考リンク


おめでとうございます! 10日間でJavaの基礎をマスターしました。ここで学んだ知識は、Webアプリケーション開発、Android開発、エンタープライズシステムなど、あらゆるJava開発の土台になります。