Java | 1 日 90 分 × 7 日アプリ学習 初級編:ファイル保存アプリ

Web APP Java
スポンサーリンク

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行を「日付+メモ」の形式にする
「何番目のメモか」を番号付きで表示する
特定の行だけ削除したり、編集したりする

でも、その土台になるのは、
今日やった 「追記」と「全行読み込み」 です。

ここがしっかりイメージできていれば、
あとは「行の中身のルール」を決めていくだけで、
どんどんアプリらしくなっていきます。

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