2日目のゴール
2日目のテーマは
「1件だけのメモ」から一歩進んで、
「複数行のメモ(履歴)をファイルに“追記”していく感覚をつかむこと」 です。
1日目は「最新の1件だけ」を上書き保存しました。
今日はこう思えるところまで行きます。
「書いたメモが、どんどん下にたまっていく」
「あとから開くと、“履歴”として読める」
これが、永続化のもう一歩先の姿です。
上書き保存と追記保存の違いを、ちゃんとイメージする
上書き保存は「ノートを毎回まっさらにする」イメージ
1日目のコードでは、こう書いていました。
new FileWriter(FILE_NAME)
Javaこれは「上書きモード」です。
すでに memo.txt が存在していた場合、
中身を全部消してから、新しい内容を書き込みます。
イメージとしては、
ノートの1ページ目に毎回書いて
書く前に必ず消しゴムで全部消している
そんな感じです。
「最新の状態だけあればいい」
という用途には向いていますが、
履歴は残りません。
追記保存は「ノートの次の行に書き足していく」イメージ
今日やりたいのは「追記」です。
すでにファイルに何か書いてあっても、
それを消さずに、
「一番下に新しい行を足していく」 動きです。
ノートの下にどんどん書き足していく
日記帳に毎日1行ずつ増えていく
そんなイメージです。
Java では、
この「追記モード」を指定するために、FileWriter のコンストラクタにもう一つ引数を渡します。
new FileWriter(FILE_NAME, true)
Javaこの true が、
「追記モードで開いてね」 という合図です。
追記モードでメモをどんどん溜めていく
1日目の saveMemo を「追記版」に書き換える
まずは、保存部分だけを追記モードに変えてみます。
private static void saveMemo(Scanner scanner) {
System.out.print("保存したいメモを1行で入力してください: ");
String memo = scanner.nextLine();
if (memo.isEmpty()) {
System.out.println("空のメモは保存しません。");
return;
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_NAME, true))) {
writer.write(memo);
writer.newLine();
System.out.println("メモをファイルに追記しました。ファイル名: " + FILE_NAME);
} catch (IOException e) {
System.out.println("メモの保存に失敗しました。");
System.out.println("(開発者向け情報)" + e.getMessage());
}
}
Javaここで変わったのは、たった一箇所です。
new FileWriter(FILE_NAME) // 上書き
new FileWriter(FILE_NAME, true) // 追記
Javaでも、動きは大きく変わります。
1回目に保存 → 1行目に書かれる
2回目に保存 → 2行目に書き足される
3回目に保存 → 3行目に書き足される
というふうに、
「メモが履歴としてたまっていく」 形になります。
複数行を読み込む:readLine をループで回す
1行だけ読むのと、全部読むのは紙一重
1日目は、こう書いていました。
String line = reader.readLine();
if (line == null) {
System.out.println("ファイルは空です。まだメモが保存されていません。");
} else {
System.out.println("保存されているメモ: " + line);
}
Javaこれは「最初の1行だけ読む」コードです。
複数行を全部読みたいときは、readLine() を「null になるまで繰り返す」だけです。
全行読み込みの基本パターン
try (BufferedReader reader = new BufferedReader(new FileReader(FILE_NAME))) {
String line;
boolean hasLine = false;
while ((line = reader.readLine()) != null) {
hasLine = true;
System.out.println(line);
}
if (!hasLine) {
System.out.println("ファイルは空です。まだメモが保存されていません。");
}
} catch (IOException e) {
System.out.println("メモの読み込みに失敗しました。");
System.out.println("(開発者向け情報)" + e.getMessage());
}
Javaここでのポイントを整理します。
while ((line = reader.readLine()) != null)
この一行に、かなり多くの意味が詰まっています。
reader.readLine() で1行読む
それを line に代入する
その結果が null でなければループを続けるnull になったら「もう読む行がない」と判断してループを抜ける
つまり、
「ファイルの終わりまで、1行ずつ読み続ける」
という動きになっています。
hasLine フラグは、
「1行も読めなかった(=空ファイル)」かどうかを判定するためのものです。
2日目版:履歴としてメモを溜めて、全部表示するアプリ
全体のコード
1日目のアプリをベースに、
保存は「追記」、読み込みは「全行表示」にしたバージョンです。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
public class FileSaveAppDay2 {
private static final String FILE_NAME = "memo.txt";
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println();
System.out.println("=== ファイル保存アプリ 2日目 ===");
System.out.println("1: メモを追記保存");
System.out.println("2: メモをすべて表示");
System.out.println("0: 終了");
System.out.print("番号を選んでください: ");
String choice = scanner.nextLine();
if ("0".equals(choice)) {
System.out.println("終了します。");
break;
} else if ("1".equals(choice)) {
saveMemo(scanner);
} else if ("2".equals(choice)) {
showAllMemos();
} else {
System.out.println("不正な入力です。0, 1, 2 のいずれかを入力してください。");
}
}
scanner.close();
}
private static void saveMemo(Scanner scanner) {
System.out.print("保存したいメモを1行で入力してください: ");
String memo = scanner.nextLine();
if (memo.isEmpty()) {
System.out.println("空のメモは保存しません。");
return;
}
try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_NAME, true))) {
writer.write(memo);
writer.newLine();
System.out.println("メモをファイルに追記しました。ファイル名: " + FILE_NAME);
} catch (IOException e) {
System.out.println("メモの保存に失敗しました。");
System.out.println("(開発者向け情報)" + e.getMessage());
}
}
private static void showAllMemos() {
try (BufferedReader reader = new BufferedReader(new FileReader(FILE_NAME))) {
String line;
boolean hasLine = false;
System.out.println("=== 保存されているメモ一覧 ===");
while ((line = reader.readLine()) != null) {
hasLine = true;
System.out.println(line);
}
if (!hasLine) {
System.out.println("(まだメモが保存されていません)");
}
System.out.println("=== 一覧表示終了 ===");
} catch (IOException e) {
System.out.println("メモの読み込みに失敗しました。まだ保存されていないか、ファイルにアクセスできません。");
System.out.println("(開発者向け情報)" + e.getMessage());
}
}
}
Java重要なポイントを深掘りする
「追記モード」は、バグの温床にもなりうる
new FileWriter(FILE_NAME, true) は便利ですが、
使い方を間違えると、
「どんどんゴミが溜まっていく」 状態にもなります。
例えば、
本当は「毎回リセットしたい」のに追記してしまう
同じ内容を何度も書いてしまう
古い形式のデータと新しい形式のデータが混ざる
などです。
だからこそ、
「このファイルは“履歴”として使うのか、“最新状態”として使うのか」
を、設計の段階で意識して決めることが大事になります。
今日のアプリでは、
あえて「履歴」として使っています。
読み込みは「1行ずつ処理する」が基本形
while ((line = reader.readLine()) != null) の形は、
ファイル読み込みの超・基本パターンです。
1行ずつ読みながら、
その場で表示したり
リストに溜めたり
パースしてオブジェクトに変換したり
いろんな応用ができます。
2日目では「表示するだけ」ですが、
ToDo アプリなどでは
「1行を1タスクとして解釈する」
という方向に広げていけます。
2日目で“体に入れてほしい感覚”
「ファイルは、ただの長いテキスト」だと捉えてみる
今日のアプリで扱っている memo.txt は、
中身を開いてみると、ただのテキストです。
1行目:最初に保存したメモ
2行目:次に保存したメモ
3行目:その次に保存したメモ
というふうに、
「行の集まり」 として存在しています。
Java のコードは、
その「行の集まり」に対して、
下に1行足す(追記)
上から順に読む(全行読み込み)
という操作をしているだけです。
この感覚が持てると、
「ファイル保存」が急に身近になります。
永続化は「メモリの世界を、テキストに落とす」こと
1日目と2日目を通して、
こんなイメージを持っていてほしいです。
プログラムの中(メモリ)
→ 文字列として持っている情報
ファイルの中(永続化された世界)
→ その文字列を、行として並べたもの
writer.write(...) は「メモリ → ファイル」の矢印reader.readLine() は「ファイル → メモリ」の矢印
この二つの矢印を自由に行き来できるようになると、
アプリは一気に「生き物っぽく」なります。
次につながる話を少しだけ
今日は、
上書き保存と追記保存の違い
追記モード(FileWriter の第2引数)
複数行の読み込み(readLine のループ)
履歴としてメモを溜めるアプリ
まで来ました。
この先は、例えばこんな方向に進めます。
1行を「日付+メモ」の形式にする
「何番目のメモか」を番号付きで表示する
特定の行だけ削除したり、編集したりする
でも、その土台になるのは、
今日やった 「追記」と「全行読み込み」 です。
ここがしっかりイメージできていれば、
あとは「行の中身のルール」を決めていくだけで、
どんどんアプリらしくなっていきます。

