1. 入出力例外の階層
Java でファイルや外部リソースを扱うときは、ほぼ必ず入出力例外に出会います。これは、読み取りや書き込みで問題が起きた場合に投げられる(throw される)オブジェクトです。たとえば、ファイルが見つからない、アクセス権がない、あるいはディスクが突然「疲れて」しまった場合などです。
この講義の主役
Java にはこれらの例外の階層があります。主なものは以下です。
- IOException — すべての入出力エラーの基底クラス。ファイル、ストリーム、ネットワークで何か問題が起きたら、たいていはこれ(または多数いるそのサブクラス)の出番です。
- FileNotFoundException — IOException の「子クラス」。存在しないファイルを開こうとした、またはパスが間違っているときに発生します。
その他の派生:
- EOFException — こちらも IOException の直接の「子」。読み取り中に予期せずファイル終端に到達したことを示します。
- MalformedInputException — 「孫」: CharacterCodingException を継承し、CharacterCodingException は IOException を継承します。指定したエンコーディングでファイルを正しく解釈できない場合に発生します(たとえば "UTF-8" を想定しているのに壊れたバイト列が来たなど)。
- ほかにも SocketException、ZipException など用途特化の「親戚」があります。階層が深くなるほど、対象とする状況はより限定的で特殊になります。
簡略図:
java.lang.Exception
└── java.io.IOException
├── java.io.FileNotFoundException
├── java.io.EOFException
├── java.nio.charset.MalformedInputException
└── ... (その他)
豆知識:
Java ではファイル操作の多くで IOException の宣言または処理が必要です。いわゆる checked 例外で、コンパイラがエラー処理の書き忘れを防いでくれます。
2. これらの例外が発生するのはいつ、なぜか
存在しないファイルを開く
最もよくあるのは、開こうとしたファイルが存在しないケースです。時刻表自体に存在しないバスを待っているようなものです。
FileInputStream fis = new FileInputStream("abracadabra.txt"); // 例外発生: FileNotFoundException
権限なしでのファイル書き込み
書き込み権限のないフォルダにファイルを書こうとすると、状況に応じて FileNotFoundException や IOException が発生します.
FileOutputStream fos = new FileOutputStream("/system/secret.txt"); // 例外発生: FileNotFoundException または IOException
媒体の破損などによる読み書きエラー
ファイルは存在していても、ディスク障害、他プログラムによる占有、突然の停電などで、さまざまなメッセージを伴う IOException が発生することがあります。
その他の原因
- 読み取り専用で開いているのに、書き込みを行おうとした。
- パスが長すぎる、または不正な文字が含まれている。
- ファイルが別のプロセスで使用中。
- ディスク容量が不足している。
- チェックと使用の間に、別プロセスによりファイルが削除された。
3. try-catch による処理
ファイル、ストリーム、ネットワークを扱うときは常に try-catch を使いましょう。いわばエアバッグのようなもので、問題が起きてもアプリが落ちず、適切に対応できます。
例外はどう捕捉すべきか
Java では、より具体的な例外を先に、より一般的な例外を後に捕捉します。最初に一般的な catch(IOException e)を書いてしまうと、より狭い FileNotFoundException などが到達できません。
正しい構造:
try {
// ファイル操作
} catch (FileNotFoundException e) {
// 「ファイルが見つからない」場合の処理
} catch (IOException e) {
// その他の入出力エラーの処理
}
なぜか?
FileNotFoundException は IOException の特殊ケースだからです。先に一般的なものを捕まえると、特殊ケースは自分の catch に到達できません。
コード例: ファイルを開くときのエラー処理
import java.io.*;
public class FileReaderExample {
public static void main(String[] args) {
String filename = "notes.txt";
try {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line = reader.readLine();
System.out.println("ファイルの最初の行: " + line);
reader.close();
} catch (FileNotFoundException e) {
System.out.println("ファイルが見つかりません: " + filename);
} catch (IOException e) {
System.out.println("ファイルの読み取り中にエラーが発生しました: " + e.getMessage());
}
}
}
注意:
ファイルの存在を事前に確認していても、try-catch は必ず残しましょう(たとえば、別プロセスが突然削除する可能性があります)。
4. 実践: エラー処理付きのコードを書く
簡単な例を作成し、ファイル不在やその他のエラーに正しく対応できるようにします。
ステップ 1: 存在しないファイルを開いてみる
import java.io.*;
public class NotesApp {
public static void main(String[] args) {
String filename = "my_notes.txt";
try {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line;
System.out.println("あなたのメモ:");
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (FileNotFoundException e) {
System.out.println("おっと!メモのファイルが見つかりません: " + filename);
System.out.println("ヒント: ファイルを作成するか、名前を確認してください。");
} catch (IOException e) {
System.out.println("ファイルの読み取り中にエラーが発生しました: " + e.getMessage());
}
}
}
ステップ 2: ファイル書き込みの処理を追加する
import java.io.*;
public class NotesWriter {
public static void main(String[] args) {
String filename = "my_notes.txt";
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(filename, true)); // append: true
writer.write("新しいメモ!\n");
writer.close();
System.out.println("メモを追加しました!");
} catch (IOException e) {
System.out.println("ファイルへの書き込み中にエラーが発生しました: " + e.getMessage());
}
}
}
ステップ 3: エラーログ出力付きの汎用例
import java.io.*;
public class SafeFileCopier {
public static void main(String[] args) {
String source = "source.txt";
String target = "target.txt";
try {
BufferedReader reader = new BufferedReader(new FileReader(source));
BufferedWriter writer = new BufferedWriter(new FileWriter(target));
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
reader.close();
writer.close();
System.out.println("ファイルを正常にコピーしました!");
} catch (FileNotFoundException e) {
System.out.println("ファイルが見つかりません: " + e.getMessage());
} catch (IOException e) {
System.out.println("入出力エラー: " + e.getMessage());
}
}
}
ヒント:
実際のアプリケーションでは、画面出力だけでなくファイルやジャーナルにもエラーを記録して、原因を後から追跡できるようにしましょう。
5. 表: 主要な入出力例外
| 例外 | 発生タイミング | 対処方法 |
|---|---|---|
|
ファイルが見つからない、パスが存在しない、権限がない | ユーザーに通知し、パス/権限を確認し、必要ならファイルを作成する |
|
読み取り中の予期しない EOF | 破損/不完全である旨を知らせ、可能ならデータを部分的に復旧する |
|
汎用の入出力エラー(ディスク、権限、ロックなど) | 詳細を確認し、操作を正しく終了し、可能であれば再試行する |
|
不正なエンコーディングまたはファイル構造 | 破損を通知し、別のエンコーディング/ソースを試す |
6. 入出力例外処理でありがちなミス
ミス No.1: Exception だけを捕まえる。 つい catch(Exception e)だけを書きたくなりますが、これでは何が起きたのかを区別できません。まずは具体的な例外(FileNotFoundException)を捕まえ、その後に一般的な IOException を捕まえるのがよいでしょう。
ミス No.2: エラー時にストリームを閉じない。 ファイルを開いた後に例外が発生すると、ストリームが閉じられないことがあります。try-with-resources を使うか、finally でリソースを閉じてください。
ミス No.3: 例外メッセージを無視する。 単に「エラー!」と出すだけではなく、詳細(e.getMessage())を表示しましょう。原因の特定が早くなります。
ミス No.4: 書き込み時に FileNotFoundException を処理しない。 FileNotFoundException は読み取りだけの話だと思われがちですが、実は書き込み時にも発生します(パスが不正、ファイル作成権限がないなど)。
ミス No.5: アクセス権を確認しない。 制限付き権限でアプリを実行していると、多くのファイル操作がエラーになります。常にこの点を考慮し、ユーザーに情報を提供しましょう。
GO TO FULL VERSION