Java | 1 日 90 分 × 7 日アプリ学習 初級編:例外処理アプリ

Web APP Java
スポンサーリンク

4日目のゴール

4日目のテーマは
「try / catch を“アプリ全体の設計”の中でどう置くかを意識する」 ことです。

ここまでであなたは、

例外とは何か
例外の種類ごとに catch する意味
事前チェックと例外の役割分担
リトライや終了条件を含めた“振る舞いの設計”

を体で感じてきました。

4日目では、もう一歩進んで、

「例外を上に投げる」という発想
「どの層で例外を握るか」という設計
ファイル読み込みのような“よくある異常系”への向き合い方

を、小さなファイル読み込みアプリを通して掴んでいきます。


今日の題材:ファイル読み込みミニアプリ

何を作るか

今日の例題は、こんなアプリです。

コンソールで「ファイル名」を入力する
そのファイルの中身を1行ずつ表示する
ファイルが存在しない/読めないときは、メッセージを出す

ここで登場するのが、
FileNotFoundExceptionIOException といった
「ファイルまわりの典型的な例外」です。

なぜファイル読み込みなのか

ファイル読み込みは、
「正常にいくとは限らない処理」の代表例 です。

ユーザーが指定したファイルが存在しないかもしれない
権限がなくて読めないかもしれない
途中でディスクエラーが起きるかもしれない

つまり、
「ちゃんと例外処理を考えないと、簡単に落ちる処理」です。

ここに try / catch をどう置くかが、
今日のメインテーマです。


まずは“例外を投げっぱなし”の読み込みメソッドを書く

ファイルを読むメソッドを用意する

まずは、
「例外を自分では処理せず、呼び出し元に投げる」メソッドを書きます。

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileLoader {

    public static void printFile(String fileName) throws IOException {
        BufferedReader reader = new BufferedReader(new FileReader(fileName));
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } finally {
            reader.close();
        }
    }
}
Java

ここでの重要ポイントは二つです。

printFile メソッドの宣言に throws IOException がついている
メソッドの中では try / catch していない(finally だけ使っている)

throws IOException は、
「このメソッドの中で IO 系の例外が起きたら、呼び出し元に投げますよ」
という宣言です。

finally だけ使っている理由

try { ... } finally { ... } という形にしているのは、

読み込み中に例外が起きても
最後に必ず reader.close() を呼びたいから

です。

ここではあえて catch を書かず、
「例外が起きたら上に投げる」設計にしています。


呼び出し側で try / catch する:責務の分担

main 側で例外を受け止める

次に、この FileLoader.printFile を呼ぶ側を書きます。

import java.io.IOException;
import java.util.Scanner;

public class FileApp {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        System.out.print("読み込みたいファイル名を入力してください: ");
        String fileName = scanner.nextLine();

        try {
            FileLoader.printFile(fileName);
            System.out.println("=== 読み込み完了 ===");
        } catch (IOException e) {
            System.out.println("ファイルを読み込めませんでした。ファイル名や権限を確認してください。");
            System.out.println("(開発者向け情報)" + e.getMessage());
        }

        scanner.close();
    }
}
Java

ここでのポイントは、

ファイルを読むロジックは FileLoader に閉じ込めた
「読めなかったときにどう振る舞うか」は main が決めている

という責務の分担です。

FileLoader は「技術的な処理担当」
FileApp(main)は「ユーザーとの会話担当」

例外は、
「技術的な失敗を、ユーザー向けのメッセージに翻訳するきっかけ」
として使っています。


throws を使う意味を、ちゃんと腹落ちさせる

「ここでは決めない。上の人に決めてもらう」

throws IOException は、
「このメソッドの中で例外が起きたとき、ここでは処理しません」
という宣言です。

つまり、

FileLoader は「読めなかった」という事実だけを伝える
それをどう扱うか(再試行するか、諦めるか、ログだけ残すか)は
呼び出し元(ここでは main)が決める

という設計になります。

これは、
「例外処理の責任を、どの層が持つかを決める」
という、とても大事な考え方です。

なんでもかんでもその場で catch しない

もし FileLoader の中で、
こんなふうに書いてしまったらどうでしょう。

public static void printFile(String fileName) {
    try {
        BufferedReader reader = new BufferedReader(new FileReader(fileName));
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } finally {
            reader.close();
        }
    } catch (IOException e) {
        System.out.println("エラーです");
    }
}
Java

これだと、

呼び出し元は「エラーです」と表示されたかどうかも分からない
ユーザーにどう伝えるかを main 側でコントロールできない
ログを残したり、リトライしたり、といった判断もできない

つまり、
「例外をその場で握りつぶしてしまっている」 状態になります。

4日目では、
「例外をどこで握るか」を意識して、
あえて上に投げる設計を体に入れてほしいのです。


try-with-resources という“片付け忘れ防止”の書き方

finally で close するのは、ちょっと面倒

さっきの FileLoader では、
finally の中で reader.close() を呼んでいました。

これはこれで正しいのですが、
Java にはもっと楽な書き方があります。

それが try-with-resources です。

try-with-resources 版の FileLoader

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileLoader {

    public static void printFile(String fileName) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
        // ここに finally は不要。try の終わりで自動的に close される
    }
}
Java

try ( ... ) { ... } のカッコの中に
「close すべきリソース」を書いておくと、

ブロックを抜けるときに自動で close() してくれます
例外が起きても、必ず close されます

つまり、
「片付け忘れを自動で防いでくれる try」 です。

ファイル、ソケット、データベース接続など、
「開いたら閉じる必要があるもの」を扱うときの定番パターンです。


異常系を“ユーザー体験”として設計する

ファイルがないとき、どう振る舞うか

今の FileApp は、
ファイルが読めなかったらメッセージを出して終わります。

ここに、
「もう一度ファイル名を入力してもらう」
というリトライの発想を足してみます。

import java.io.IOException;
import java.util.Scanner;

public class FileApp {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        while (true) {
            System.out.print("読み込みたいファイル名を入力してください(q で終了): ");
            String fileName = scanner.nextLine();
            if ("q".equalsIgnoreCase(fileName)) {
                System.out.println("終了します。");
                break;
            }

            try {
                FileLoader.printFile(fileName);
                System.out.println("=== 読み込み完了 ===");
            } catch (IOException e) {
                System.out.println("ファイルを読み込めませんでした。ファイル名や権限を確認してください。");
                System.out.println("(開発者向け情報)" + e.getMessage());
            }

            System.out.println();
        }

        scanner.close();
    }
}
Java

ここでのポイントは、

例外が起きてもアプリ自体は続ける
ユーザーに「q で終了」という逃げ道を用意している
異常系も含めて“会話の流れ”として設計している

ということです。

「異常系もストーリーの一部」という感覚

3日目までの割り算アプリと同じように、
ファイルアプリでも、

正常系:ファイルが読める → 中身を表示して完了
異常系:ファイルが読めない → メッセージを出して、もう一度入力してもらう

という“ストーリー”を設計しています。

例外処理は、
「ストーリーから外れた瞬間を、どう物語に戻すか」
を決めるための仕組みだ、と捉えてみてください。


4日目で絶対に押さえてほしい本質

今日いちばん大事なのは、
「例外をどこで投げて、どこで受け止めて、どこでユーザーに伝えるかは“設計の選択”だ」
という感覚です。

ファイル読み込みという“失敗しやすい処理”を題材にして、

メソッド側では throws で「失敗しうること」を宣言する
呼び出し側で try / catch を書き、ユーザー向けのメッセージに翻訳する
リソースは finally や try-with-resources で必ず片付ける
異常系も含めて「アプリの会話の流れ」として設計する

ここまで来ると、
例外処理はもう「赤いエラーを消すためのもの」ではなく、
「アプリの信頼性と体験をデザインするための道具」
として見えてくるはずです。

明日以降は、
「自分で例外クラスを作る」「例外を設計の一部として定義する」
といったところにも、少しずつ足を伸ばしていけます。

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