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開発の土台になります。