例外処理 (J)

はじめに

I/Oに関しては、プログラム外とのやりとりになります。ということは、正しいプログラムであっても、外部の事情で当初の意図どおり動けないことがあります。

Java では、そのような処理は例外とし扱い、java.io.IOException などについては、例外の扱いの明確化を要求します。 ネットワークアクセスの場合も、普通のファイル I/O の場合も、しばしば例外処理が要求されます。

Java では、

  • 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 は、

これらのクラスを catch 節に書いた場合も FileNotFoundException は catch できます。

複数のクラスの exception をまとめて処理したい場合は以下のように記載可能です。

} catch (Exception1 | Exception 2 e) {
    // Exception1, Exception2 に共通の型操作
}

catch-finally 節の利用法

catch 節に関する使い方のおすすめは以下のとおり。

  • 一般的な利用としては、Exception があった理由を表示して、終了する。あるいは、ユーザに次の処理を入力してもらって、次の動作を行う。
  • 絶対、Exception が起きないと確信していたら、絶対起きない旨書いておく。
} catch (Exception e) {
    throw new Error("This SHOULD NOT occure!"); // あるいは、 assert false;
}
  • デバッグ中なら、下記のようにバグ情報出力をおこなうとよいでしょう。
} catch (Exception e) {
    e.printStackTrace();
}
  • あるいは、今回のように例外をそのまま throw する。例外が起きたら、ターミナルに状況が表示されます(先の printStackTrace() 相当ですね)。

  • やってはいけないこと: 良くわからないからといって、見なかったことにする。先送りしても、別のバグと混ざって分かりにくくなるだけです。自分で自分にトラップ仕掛けるようなものです。絶対止めましょう。

} catch (Exception e) {
    /* みなかったことにしよう。。。 */
}
/* 事態は、さらに混迷を深める */

ちなみに、finally 節のよくある利用法は、

  • 後始末で行うような、ファイルを close() したり、ネットワーク処理で使った socket や port を閉じたり

というのが普通です。ただ、最近は次に紹介する記法をつかうことが多いようです。

Automatic Resource Management

I/O では、stream を開いて、一連の処理をした後で、例外の有無にかかわらず stream を閉じるってプログラムの形は多くなります。

このようなプログラムは、

try ( /* stream などを開く */ ) {
  /* 一連の処理 */
} /* catch 節や finally 節も記述可能 */

という形で書くこともできます。皆さんに見せたプログラムでは、こちらの記法を採用しています。

実は、InputStream や Socket などは、すべてjava.lang.AutoCloseable interface を継承していて、close() メソッドで自動資源解放ができるような統一規格になっています。