例外処理
はじめに
ファイル I/Oに関しては、プログラム外とのやりとりになります。ということは、正しいプログラムであっても、引数でもらった名前のファイルが開けなかったりすると、当初の意図どおりは動けない訳です。
Java では、そのような処理は例外とし扱い、java.io.IOException などについては、例外の扱いの明確化を要求します。
つまり、
try-catch-finally
ブロックを用いて、例外処理するか- メソッドに
throws Exception
宣言をして、例外が起きることを明示するか
です。
例外処理は、こんな感じ。catch
節とfinally
節のどちらか一方だけでも大丈夫です。
try {
// 例外を起こすかもしれない処理
// を含んだブロック
} catch (Exception1 e1) { // try ブロック内で Exception1 型の例外が起きたら、
// ここで捕まえる。対象の例外オブジェクトは e1 ね。
// 例外処理内容1
} catch (Exception2 e2) { // try ブロック内で起きた例外が前のハンドラで捕まらず、
// ここまでもれてきたら、Exception2 型なら受け取るよ
// 例外処理内容2
} finally { // finally 節
// 例外の有無に関わらず、実行して欲しい事柄を書く場所
}
これで、例外に応じた処理を書くことができます。 ちなみに、java.io.FileInputStream のコンストラクタで起きる java.io.FileNotFoundException は、
- java.io.IOException の子クラスで、
- java.lang.Exception の孫クラスでもあるので、
これらのクラスを catch 節に書いた場合も FileNotFoundException
は catch できます。
catch-finally 節の利用法
catch 節に関する使い方のおすすめは以下のとおり。
- 一般的な利用としては、Exception があった理由を表示して、終了する。あるいは、ユーザに次の処理を入力してもらって、次の動作を行う。
- 絶対、Exception が起きないと確信していたら、絶対起きない旨書いておく。
} catch (Exception e) {
throw new Error("This SHOULD NOT occure!"); // あるいは、 assert false;
}
- デバッグ中なら、下記のようにバグ情報出力をおこなうとよいでしょう。
} catch (Exception e) {
e.printStackTrace();
}
- やってはいけないこと: 良くわからないからといって、見なかったことにする。先送りしても、別のバグと混ざって分かりにくくなるだけです。自分で自分にトラップ仕掛けるようなものです。絶対止めましょう。
} catch (Exception e) {
/* みなかったことにしよう。。。 */
}
/* 事態は、さらに混迷を深める */
ちなみに、finally 節のよくある利用法は、
- 後始末で行うような、ファイルを close() したり、ネットワーク処理で使った socket や port を閉じたり
というのが普通です。
Automatic Resource Management
ファイル I/O では、stream を開いて、一連の処理をした後で、例外の有無にかかわらず stream を閉じるってプログラムの形は多くなります。 このようなプログラムは、
try ( /* stream を開く */ ) {
/* 一連の処理 */
}
という形で書くこともできます。
try (InputStream in = new FileInputStream(from); OutputStream out = new FileOutputStream(to)) {
while (true) {
/* in から read。IOException の可能性 */
int size = in.read(buf, 0, BUFSIZE);
if (size < 0) { /* EOF なら、loop を抜けて */
break;
} else {
/* size 分, out に出力。IOException の可能性 */
out.write(buf, 0, size);
}
}
}