Javaの例外処理

今回はQiitaのアドベントカレンダーの投稿日ということもあって、Javaでの例外処理のやり方について、さらっと紹介していこうと思います。

1. プログラムにおける例外とは

そもそもプログラムにおける例外は何かというと、一言で言うと「文法としては正しいにも関わらず、実行すると想定外の状況が発生し、プログラムが実行不能になること」と言うことになります。
まず厄介なのが、「文法としては正しい」ので、コンパイルが通ってしまうことです。なので、「よっしゃ、コンパイルが通ったぞ!」と思って実行したら、「あれ!?」と言うことになるわけです。
なので、どのような値が来るとどうなって、実際に起こり得るけど起こったらまずい状況とはどう言う場合かを考えて、それに応じて然るべき対応ができるようにする必要があるわけです。
次に、具体的に「どう言う状況が例外に当たるか」の例をいくつかあげて見ます。
・nullの領域にアクセスして何かやろうとした
・配列やリストなどの要素数より大きい数字のインデックスにアクセスした
・0で割り算した
・許可されていない場所にアクセスした
・ファイルに対して何かやってたらファイルが途中で消えた

2.例外処理の基本
Javaにおける例外処理の基本形は、こんな感じです。

try {
  // ここで何らかの処理
} catch (Exception e) {
  // 例外が発生したらここに来る
}

ただ、Exceptionクラスだと、nullアクセスも0割りも、その他諸々含めて一緒くたに扱われるので、一般的にはExceptionクラスのサブクラスを使って、起きた例外に応じた処理を行います。
例として、nullが入ったString変数に対して文字列の長さを取得しようとした場合を見てみます。

try {
  String str = null;
  System.out.println(str.length());
} catch (NullPointerException e) {
  System.err.println(e.getMessage());
  e.printStackTrace();
}

これで、例外の中でもNullにアクセスした場合の例外のみに対して例外処理が行われます。ちなみに、printStackTraceメソッドは例外が起きたところからのメソッド呼び出しの階層一覧を出力してくれます。結構便利なので、デバッグでよく使います。
どんな例外クラスがあるかは、Javaの公式ドキュメントを見てください。

3. 独自の例外定義とthrow、throws
Javaでは(大体の言語でそうだと思うけど)あらかじめ用意されている例外以外に、独自の例外クラスを作ることができます。
例えば、温度が100度を超えたらエラーにするとかを考えて見ます。

// このクラスで100度以上になった場合の例外を定義
class TooHotException extends Exception {
  public TooHotException(String s) {
    super(s);
  }
}
   try {
      int temperature = getTemperature();
      if (temperature > 100) {
        // 温度が100度以上だったら先ほど定義した例外クラスに投げる
        throw new TooHotException(“Too hot!”);
      }
    } catch (TooHotException e) {
      System.err.println(e.getMessage());
    }

こんな感じです。なお、ここで出てきたthrowは、呼び出すことで対応するcatch節に処理を移すのに使います。

もう1つ、throwに似た名前のthrowsという構文がありますが、これはある例外が発生したら呼び出し元に処理を任せるのに使うものです。
使い方はこんな感じです。


public void newFunc() throws NumberFormatException {
  // このメソッド内でNumberFormatException例外が発生したら、呼び出し元で例外処理を行う
}

ちなみに、NumberFormatExceptionは数値が入るべきところに数値以外のものが来た場合の例外です。

4. 例外が起きようと起きまいと共通の終了処理を行う場合
例外が発生して、何らかのエラーメッセージを出すものの、最後に何らかの共通処理を行いたい場合は、finallyを使います。

try {
  // ここで何らかの処理
} catch (Exception e) {
  // 例外が発生したらここに来る
} finally {
  // 例外が発生してもしなくても最後にここに来る
}

ファイルバッファを最後に閉じる例です。

  BufferedReader br = new BufferedReader(new FileReader(path));
  try {
    br.readLine();
  } catch (IOException e) {
    System.err.println(e.getMessage());
  } finally {
    if (br != null) br.close();
  }

ただ、Java7以降でtry-with-resources文を用いることによって、finallyを使わなくても使い終わったものを終了することができるので、紹介しておきます(こっちが使えるならこっちの方がいいです)。

  BufferedReader br = new BufferedReader(new FileReader(path));
  BufferedReader br = new BufferedReader(new FileReader(path));
  try (BufferedReader br = new BufferedReader(new FileReader(path));) {
    br.readLine();
  } catch (IOException e) {
    System.err.println(e.getMessage());
  }

5.その他注意点いくつか
例外処理におけるいわゆる禁じ手をいくつか紹介しておきます。
・例外を握り潰さない


try {
  // 何らかの処理
} catch (Exception e) {
}

これだと、例外が発生した場合でも握りつぶして次に進んでしまいますが、このようなことはせず、ちゃんと例外に応じた処理(エラーログを出して処理を終了するとか)を行いましょう。
・例外処理は本当の例外に対してのみ行う
ごく稀に、例外処理を分岐みたいな使い方をする人がいますが、このようなことをやってはいけません。分岐はちゃんとif文とかを使い、プログラムとかシステムとかで通常は起こりえないイレギュラーな事態だけcatchされるようにしましょう。

ここまでざっと例外処理について書いて来ましたが、やはり実際に色々プログラムを書いて見て慣れるのが一番です。
これを読んだみなさんも是非例外処理を使いこなせるように頑張りましょう!