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 |
ファイルパスの表現 |
重要ポイント
- リソースはtry-with-resourcesで管理する
- チェック例外は**
catchかthrows**で処理する - ファイル操作には**
Filesクラス**を使う - カスタム例外で業務ロジックのエラーを表現する
練習問題
問題1: 基本
ユーザーから数値を入力させ、0で割り算しようとした場合に適切なエラーメッセージを表示するプログラムを作成してください。
問題2: 応用
テキストファイルを読み込み、行数・単語数・文字数を表示するプログラム(簡易wcコマンド)を作成してください。
チャレンジ問題
カスタム例外を使って、簡易ログインシステムを実装してください。UserNotFoundExceptionとInvalidPasswordExceptionを定義し、ユーザー名・パスワードの検証を行いましょう。
参考リンク
次回予告: Day 9では「コレクションとジェネリクス」について学びます。List、Map、Setの使い方をマスターしましょう。