5日目のゴール
5日目のテーマは
「ファイル保存アプリを“ただ動くコード”から“役割が分かれた設計”に育てること」です。
ここまでであなたは、
メモをファイルに追記する
ID・日付・本文を1行に詰める
一覧・削除・編集を一時ファイルで再構築して実現する
というところまで来ました。
5日目では一歩引いて、
「ファイル操作のコード」と「アプリのロジック」を分ける、という設計をやります。
つまり、クラスを分けて「責務(役割)」をはっきりさせる練習です。
今日のテーマは「クラス分割」×「永続化」
何が“ごちゃっと”してきているのか
4日目までのコードは、だいたいこんな感じでした。
main メソッドの中にメニュー
同じクラスの中に
追加、一覧、削除、編集、ID採番、ファイル読み書き
が全部入っている
動くけれど、だんだんこう感じてきませんか。
どこが「メモの概念」で
どこが「ファイルの扱い」で
どこが「ユーザーとの会話」なのか
コードを追いながら頭の中で分けないといけない
5日目では、これをコードのレベルで分けます。
メモそのものを表すクラス
メモをファイルに保存・読み込みするクラス
ユーザーと対話するアプリ本体のクラス
この三つに分けるイメージです。
Memo クラスを作る:1行を“オブジェクト”で表現する
まずは「メモという概念」をコードにする
3日目で、1行の仕様をこう決めました。
ID|日付|本文
これを、そのままクラスにします。
public class Memo {
private final int id;
private final String date;
private final String body;
public Memo(int id, String date, String body) {
this.id = id;
this.date = date;
this.body = body;
}
public int getId() {
return id;
}
public String getDate() {
return date;
}
public String getBody() {
return body;
}
@Override
public String toString() {
return "ID: " + id + " / 日付: " + date + " / 本文: " + body;
}
}
Javaここでやっていることは、とてもシンプルです。
1行の中にあった「ID」「日付」「本文」を
それぞれフィールドとして持たせているだけです。
重要なのは、
「ファイルの1行」を直接扱うのではなく、
「メモという意味のある塊」として扱えるようにしたことです。
これで、アプリの中では
「Memo のリスト」として考えられるようになります。
MemoRepository クラスを作る:ファイルとのやり取り担当
「ファイルのことはこのクラスに任せる」という発想
次に、ファイル読み書き専用のクラスを作ります。
名前はよくあるパターンで MemoRepository にしてみましょう。
役割はこうです。
メモを追加する
メモを全部読み込む
メモを削除する
メモを編集する
次のIDを決める
つまり、「メモとファイルの橋渡し役」です。
MemoRepository の骨格
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class MemoRepository {
private final String fileName;
public MemoRepository(String fileName) {
this.fileName = fileName;
}
public List<Memo> findAll() throws IOException {
List<Memo> result = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
Memo memo = parseLine(line);
if (memo != null) {
result.add(memo);
}
}
} catch (FileNotFoundException e) {
// ファイルがまだない場合は、空リストを返す
}
return result;
}
public void add(String date, String body) throws IOException {
int nextId = getNextId();
Memo memo = new Memo(nextId, date, body);
String line = formatLine(memo);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName, true))) {
writer.write(line);
writer.newLine();
}
}
public boolean deleteById(int targetId) throws IOException {
String tempFileName = fileName + ".tmp";
boolean deleted = false;
try (BufferedReader reader = new BufferedReader(new FileReader(fileName));
BufferedWriter writer = new BufferedWriter(new FileWriter(tempFileName))) {
String line;
while ((line = reader.readLine()) != null) {
Memo memo = parseLine(line);
if (memo != null && memo.getId() == targetId) {
deleted = true;
continue;
}
writer.write(line);
writer.newLine();
}
} catch (FileNotFoundException e) {
return false;
}
replaceFile(tempFileName);
return deleted;
}
public boolean updateBody(int targetId, String newBody) throws IOException {
String tempFileName = fileName + ".tmp";
boolean updated = false;
try (BufferedReader reader = new BufferedReader(new FileReader(fileName));
BufferedWriter writer = new BufferedWriter(new FileWriter(tempFileName))) {
String line;
while ((line = reader.readLine()) != null) {
Memo memo = parseLine(line);
if (memo != null && memo.getId() == targetId) {
Memo updatedMemo = new Memo(memo.getId(), memo.getDate(), newBody);
String newLine = formatLine(updatedMemo);
writer.write(newLine);
writer.newLine();
updated = true;
} else {
writer.write(line);
writer.newLine();
}
}
} catch (FileNotFoundException e) {
return false;
}
replaceFile(tempFileName);
return updated;
}
private int getNextId() throws IOException {
int lastId = 0;
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
while ((line = reader.readLine()) != null) {
Memo memo = parseLine(line);
if (memo != null) {
lastId = memo.getId();
}
}
} catch (FileNotFoundException e) {
// まだファイルがない場合は lastId = 0 のまま
}
return lastId + 1;
}
private Memo parseLine(String line) {
String[] parts = line.split("\\|", 3);
if (parts.length != 3) {
return null;
}
try {
int id = Integer.parseInt(parts[0]);
String date = parts[1];
String body = parts[2];
return new Memo(id, date, body);
} catch (NumberFormatException e) {
return null;
}
}
private String formatLine(Memo memo) {
return memo.getId() + "|" + memo.getDate() + "|" + memo.getBody();
}
private void replaceFile(String tempFileName) throws IOException {
File original = new File(fileName);
File temp = new File(tempFileName);
if (!original.delete()) {
throw new IOException("元ファイルを削除できませんでした: " + fileName);
}
if (!temp.renameTo(original)) {
throw new IOException("一時ファイルをリネームできませんでした: " + tempFileName);
}
}
}
Java一気に出しましたが、やっていることは4日目までと同じです。
違うのは「メモを行として扱う」のではなく「Memo オブジェクトとして扱う」ようにしたことです。
findAll は「全部読み込んで List<Memo> を返す」
add は「Memo を1件ファイルに追記する」
deleteById は「そのIDの行だけ書き出さない」
updateBody は「そのIDの行だけ書き換えて書き出す」
という役割を持っています。
アプリ本体は「ユーザーとの会話」に集中させる
main 側は、もうファイルのことを知らなくていい
Memo と MemoRepository ができたので、
アプリ本体はかなりスッキリ書けます。
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
public class FileSaveAppDay5 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
MemoRepository repo = new MemoRepository("memo.txt");
while (true) {
System.out.println();
System.out.println("=== ファイル保存アプリ 5日目 ===");
System.out.println("1: メモを追加");
System.out.println("2: メモ一覧を表示");
System.out.println("3: メモを削除");
System.out.println("4: メモを編集");
System.out.println("0: 終了");
System.out.print("番号を選んでください: ");
String choice = scanner.nextLine();
try {
if ("0".equals(choice)) {
System.out.println("終了します。");
break;
} else if ("1".equals(choice)) {
addMemo(scanner, repo);
} else if ("2".equals(choice)) {
showAllMemos(repo);
} else if ("3".equals(choice)) {
deleteMemo(scanner, repo);
} else if ("4".equals(choice)) {
editMemo(scanner, repo);
} else {
System.out.println("不正な入力です。0〜4 のいずれかを入力してください。");
}
} catch (IOException e) {
System.out.println("ファイル操作中にエラーが発生しました。");
System.out.println("(開発者向け情報)" + e.getMessage());
}
}
scanner.close();
}
private static void addMemo(Scanner scanner, MemoRepository repo) throws IOException {
System.out.print("メモ本文を1行で入力してください: ");
String body = scanner.nextLine();
if (body.isEmpty()) {
System.out.println("空のメモは保存しません。");
return;
}
String date = "2026-06-06"; // 後で LocalDate.now() に差し替え可能
repo.add(date, body);
System.out.println("メモを追加しました。");
}
private static void showAllMemos(MemoRepository repo) throws IOException {
List<Memo> memos = repo.findAll();
if (memos.isEmpty()) {
System.out.println("まだメモが保存されていません。");
return;
}
System.out.println("=== メモ一覧 ===");
for (Memo memo : memos) {
System.out.println(memo.toString());
}
System.out.println("=== 一覧表示終了 ===");
}
private static void deleteMemo(Scanner scanner, MemoRepository repo) throws IOException {
System.out.print("削除したいメモのIDを入力してください: ");
String idStr = scanner.nextLine();
if (idStr.isEmpty()) {
System.out.println("ID が入力されていません。");
return;
}
int id;
try {
id = Integer.parseInt(idStr);
} catch (NumberFormatException e) {
System.out.println("ID は整数で入力してください。");
return;
}
boolean deleted = repo.deleteById(id);
if (deleted) {
System.out.println("ID " + id + " のメモを削除しました。");
} else {
System.out.println("指定されたIDのメモが見つかりませんでした。");
}
}
private static void editMemo(Scanner scanner, MemoRepository repo) throws IOException {
System.out.print("編集したいメモのIDを入力してください: ");
String idStr = scanner.nextLine();
if (idStr.isEmpty()) {
System.out.println("ID が入力されていません。");
return;
}
int id;
try {
id = Integer.parseInt(idStr);
} catch (NumberFormatException e) {
System.out.println("ID は整数で入力してください。");
return;
}
System.out.print("新しい本文を入力してください: ");
String newBody = scanner.nextLine();
if (newBody.isEmpty()) {
System.out.println("空の本文には編集しません。");
return;
}
boolean updated = repo.updateBody(id, newBody);
if (updated) {
System.out.println("ID " + id + " のメモを編集しました。");
} else {
System.out.println("指定されたIDのメモが見つかりませんでした。");
}
}
}
Javaここで注目してほしいのは、
main 側はもう「ファイルの行」を一切触っていないことです。
ユーザーから入力をもらう
MemoRepository に「こうして」とお願いする
結果に応じてメッセージを出す
という「司会進行」だけをやっています。
5日目でいちばん大事な感覚
「ファイルのことは、専任クラスに任せる」
これまでの数日間で、
あなたはファイル読み書きの細かい手順を
かなり体で覚えてきました。
5日目でやったのは、
その知識を「一箇所にまとめて閉じ込める」ことです。
MemoRepository に閉じ込めることで、
アプリ本体は「メモ」という概念だけを考えればよくなる
ファイルの形式を変えたくなっても、MemoRepository だけ直せばいい
テストするときに、MemoRepository を差し替える、という発想も取れる
という、設計としての強さが出てきます。
永続化は「技術」から「役割分担」へ
1〜4日目は、
「どうやって書くか」「どうやって読むか」という
技術寄りの話が中心でした。
5日目は、
「誰がそれを担当するのか」という
役割分担の話に踏み込んでいます。
メモという“意味のある塊”を表す Memo
メモとファイルの橋渡しをする MemoRepository
ユーザーと会話する FileSaveAppDay5
この三つに分けられた時点で、
あなたのファイル保存アプリは
「ただ動くコード」から
「設計された小さなアプリ」に変わっています。
ここまで来ているなら、
もう「ファイル保存が怖い初心者」ではなく、
「永続化を設計できる側」に足を踏み入れています。

