10日で覚えるJavaDay 8: 例外処理とファイルI/O

Day 8: 例外処理とファイルI/O

今日学ぶこと

  • 例外の基本(try-catch-finally)
  • 例外の種類(チェック例外・非チェック例外)
  • カスタム例外
  • ファイルの読み書き
  • try-with-resources

例外とは

例外(Exception)はプログラム実行中に発生するエラーです。適切に処理しないとプログラムが異常終了します。

// 例外が発生する例
int[] nums = {1, 2, 3};
System.out.println(nums[5]); // ArrayIndexOutOfBoundsException

String s = null;
s.length(); // NullPointerException

int result = 10 / 0; // ArithmeticException

try-catch-finally

try {
    int result = 10 / 0;
    System.out.println(result);
} catch (ArithmeticException e) {
    System.out.println("エラー: " + e.getMessage());
} finally {
    System.out.println("必ず実行される");
}
flowchart TB
    Try["try ブロック<br>例外が発生するかもしれない処理"]
    Catch["catch ブロック<br>例外をキャッチして処理"]
    Finally["finally ブロック<br>必ず実行される"]
    Success["正常終了"]
    Try -->|"例外発生"| Catch --> Finally
    Try -->|"例外なし"| Success --> Finally
    style Try fill:#3b82f6,color:#fff
    style Catch fill:#ef4444,color:#fff
    style Finally fill:#22c55e,color:#fff

複数の例外をキャッチ

try {
    String input = "abc";
    int number = Integer.parseInt(input);
    int result = 100 / number;
} catch (NumberFormatException e) {
    System.out.println("数値に変換できません: " + e.getMessage());
} catch (ArithmeticException e) {
    System.out.println("計算エラー: " + e.getMessage());
} catch (Exception e) {
    System.out.println("予期しないエラー: " + e.getMessage());
}

// マルチキャッチ(Java 7+)
try {
    // ...
} catch (NumberFormatException | ArithmeticException e) {
    System.out.println("入力または計算エラー: " + e.getMessage());
}

ポイント: catch具体的な例外から順番に書きます。Exception(すべての親)は最後に書きます。


例外の階層

flowchart TB
    Throwable["Throwable"]
    Error["Error<br>(回復不能)"]
    Exception["Exception"]
    RuntimeException["RuntimeException<br>(非チェック例外)"]
    IOException["IOException<br>(チェック例外)"]
    NPE["NullPointerException"]
    IAE["IllegalArgumentException"]
    AIOOBE["ArrayIndexOutOfBoundsException"]
    Throwable --> Error
    Throwable --> Exception
    Exception --> RuntimeException
    Exception --> IOException
    RuntimeException --> NPE
    RuntimeException --> IAE
    RuntimeException --> AIOOBE
    style Error fill:#ef4444,color:#fff
    style RuntimeException fill:#f59e0b,color:#fff
    style IOException fill:#3b82f6,color:#fff
種類 catch必須?
チェック例外 IOException, SQLException はい
非チェック例外 NullPointerException, IllegalArgumentException いいえ
Error OutOfMemoryError, StackOverflowError いいえ(通常キャッチしない)

throws と throw

throws(メソッドが例外を投げることを宣言)

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

// チェック例外を呼び出し元に委譲
String readFile(String path) throws IOException {
    return Files.readString(Path.of(path));
}

throw(例外を明示的に投げる)

void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年齢は0以上です: " + age);
    }
    this.age = age;
}

カスタム例外

// チェック例外
class InsufficientFundsException extends Exception {
    private final double amount;

    InsufficientFundsException(double amount) {
        super("残高不足: " + amount + "円が必要です");
        this.amount = amount;
    }

    double getAmount() { return amount; }
}

// 使用
class BankAccount {
    private double balance;

    BankAccount(double balance) { this.balance = balance; }

    void withdraw(double amount) throws InsufficientFundsException {
        if (amount > balance) {
            throw new InsufficientFundsException(amount - balance);
        }
        balance -= amount;
    }
}

try-with-resources

AutoCloseableを実装したリソースを自動的にクローズします。

import java.io.*;

// ❌ 従来の方法
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("data.txt"));
    String line = reader.readLine();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        try { reader.close(); } catch (IOException e) { }
    }
}

// ✅ try-with-resources(Java 7+)
try (var reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
// reader は自動的にクローズされる

推奨: ファイル操作やデータベース接続には必ずtry-with-resourcesを使いましょう。


ファイルの読み書き

Files クラス(推奨)

import java.nio.file.Files;
import java.nio.file.Path;
import java.io.IOException;
import java.util.List;

// 全文読み込み
String content = Files.readString(Path.of("data.txt"));

// 行ごとに読み込み
List<String> lines = Files.readAllLines(Path.of("data.txt"));
for (String line : lines) {
    System.out.println(line);
}

// 書き込み
Files.writeString(Path.of("output.txt"), "Hello, Java!");

// 行の書き込み
List<String> data = List.of("Line 1", "Line 2", "Line 3");
Files.write(Path.of("output.txt"), data);

ファイルの存在確認と情報

Path path = Path.of("data.txt");

Files.exists(path)          // ファイルが存在するか
Files.isRegularFile(path)   // 通常ファイルか
Files.isDirectory(path)     // ディレクトリか
Files.size(path)            // サイズ(バイト)

BufferedReader / BufferedWriter

大きなファイルの処理に適しています。

// 読み込み
try (var reader = Files.newBufferedReader(Path.of("data.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

// 書き込み
try (var writer = Files.newBufferedWriter(Path.of("output.txt"))) {
    writer.write("Hello");
    writer.newLine();
    writer.write("World");
}

ディレクトリ操作

// ディレクトリ作成
Files.createDirectories(Path.of("path/to/dir"));

// ファイル一覧
try (var stream = Files.list(Path.of("."))) {
    stream.forEach(System.out::println);
}

// ファイルコピー・移動・削除
Files.copy(Path.of("src.txt"), Path.of("dest.txt"));
Files.move(Path.of("old.txt"), Path.of("new.txt"));
Files.delete(Path.of("temp.txt"));

Scanner でファイル読み込み

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

try (var scanner = new Scanner(new File("data.txt"))) {
    while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        System.out.println(line);
    }
} catch (FileNotFoundException e) {
    System.out.println("ファイルが見つかりません: " + e.getMessage());
}

実践: CSVファイル処理

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

record Student(String name, int score) {
    String grade() {
        if (score >= 90) return "A";
        if (score >= 80) return "B";
        if (score >= 70) return "C";
        return "D";
    }

    @Override
    public String toString() {
        return String.format("%s: %d点 (%s)", name, score, grade());
    }
}

public class CsvProcessor {
    static List<Student> readCsv(Path path) throws IOException {
        List<Student> students = new ArrayList<>();
        List<String> lines = Files.readAllLines(path);

        for (int i = 1; i < lines.size(); i++) { // skip header
            String[] parts = lines.get(i).split(",");
            String name = parts[0].trim();
            int score = Integer.parseInt(parts[1].trim());
            students.add(new Student(name, score));
        }
        return students;
    }

    static void writeSummary(Path path, List<Student> students) throws IOException {
        var sb = new StringBuilder();
        sb.append("=== 成績レポート ===%n".formatted());

        double avg = students.stream()
            .mapToInt(Student::score)
            .average()
            .orElse(0);

        for (Student s : students) {
            sb.append(s).append(System.lineSeparator());
        }
        sb.append("---%n".formatted());
        sb.append("平均点: %.1f%n".formatted(avg));

        Files.writeString(path, sb.toString());
        System.out.println("レポートを出力しました: " + path);
    }

    public static void main(String[] args) {
        // サンプルCSVを作成
        try {
            String csv = """
                    name,score
                    太郎,85
                    花子,92
                    次郎,78
                    美咲,95
                    健太,67
                    """;
            Path csvPath = Path.of("students.csv");
            Files.writeString(csvPath, csv);

            List<Student> students = readCsv(csvPath);
            students.forEach(System.out::println);

            writeSummary(Path.of("report.txt"), students);
        } catch (IOException e) {
            System.err.println("ファイル操作エラー: " + e.getMessage());
        }
    }
}

まとめ

概念 説明
try-catch 例外のキャッチと処理
finally 必ず実行されるブロック
throws 例外の宣言
throw 例外の送出
チェック例外 catchまたはthrowsが必須
非チェック例外 RuntimeExceptionのサブクラス
try-with-resources リソースの自動クローズ
Files ファイル操作のユーティリティ
Path ファイルパスの表現

重要ポイント

  1. リソースはtry-with-resourcesで管理する
  2. チェック例外は**catchthrows**で処理する
  3. ファイル操作には**Filesクラス**を使う
  4. カスタム例外で業務ロジックのエラーを表現する

練習問題

問題1: 基本

ユーザーから数値を入力させ、0で割り算しようとした場合に適切なエラーメッセージを表示するプログラムを作成してください。

問題2: 応用

テキストファイルを読み込み、行数・単語数・文字数を表示するプログラム(簡易wcコマンド)を作成してください。

チャレンジ問題

カスタム例外を使って、簡易ログインシステムを実装してください。UserNotFoundExceptionInvalidPasswordExceptionを定義し、ユーザー名・パスワードの検証を行いましょう。


参考リンク


次回予告: Day 9では「コレクションとジェネリクス」について学びます。List、Map、Setの使い方をマスターしましょう。