Java例外処理(Exception)

例外処理(Exception)

例外(Exception)とは、プログラムの実行中に発生する異常な状況のことをいう。

プログラミングにおけるエラー(Error)には2種類がある。コンパイル(Compile)時に発生するエラーと、ランタイム(Runtime)時に発生するエラーである。コンパイル時に発生するエラーは、文法的な誤りや文脈に合わないコードを書いたことで発生するため、プログラム自体が実行すらできない状態である。それに対してランタイム時に発生するエラーは、プログラムの実行中に予期しない状況で発生するため、誤動作したりプログラムが終了したりする現象が起こる。

例えば、オブジェクトを宣言しただけで生成する前に参照した場合、整数を0で割った場合、ファイルにアクセスしようとしたが実際のファイルがない場合、配列のサイズを超えたインデックスにアクセスした場合など、多くの例外状況が存在する。

Javaは、このようなランタイム時に発生するエラーを例外(Exception)と呼び、例外状況を処理できるようにしている。

例外関連クラス

  • java.lang.Object
    • java.lang.Throwable
      • java.lang.Error
      • java.lang.Exception
      • java.lang.RuntimeException
        • Unchecked Exception
      • Other Exception
        • Checked Exception

例外(Exception)も結局はクラスである。そのため、すべての例外(Exception)クラスはThrowableクラスを継承しており、Throwableは最上位クラスObjectの子クラスである。

Throwableを継承するクラスにはErrorとExceptionがある。Errorは主にハードウェア関連の例外を処理するクラスであり、システムレベルの重大なエラーである。例えば、メモリ問題(OutOfMemoryError)、スタックオーバーフロー、スレッド割り込みが発生した場合などのエラーがある。この種類の例外は、コードで処理するよりも、システムに変更を加えて問題を処理する場合が一般的である。

Exceptionは大きく、RuntimeExceptionを継承した例外と、継承していない例外に分けられる。RuntimeExceptionを基準に分ける理由は、例外指定を行うかどうかによる。RuntimeExceptionに関連する例外は、別途指定しなくても大きな問題にならないことが多い。例外を指定するために必要な労力が、例外を指定して得られる効果より大きいためである。このような例外処理はJVMに任せるほうが効率的である。

RuntimeExceptionに関連する例外も、Errorと同じように一般的には別途指定しない場合が多い。一つひとつにかかる労力が、例外を指定して得られる効率よりも大きいためである。

Checked Exception、Unchecked Exception

  • Checked Exception
    • 必ず例外処理をしなければならない。
    • トランザクションRollbackは行われない。
    • RuntimeExceptionを継承しない。(X)
    • IOException, SQLException
  • Unchecked Exception
    • 例外処理をしなくてもよい。
    • トランザクションRollbackが行われる。
    • RuntimeExceptionを継承する。(O)
    • NullPointerException, IllegalArgumentException

基本的な例外処理 - try~catch~finally

例外処理をメソッド内で直接行うには、try、catch、finallyキーワードを使用する。

try {
  // tryブロック
} catch (例外型1 引数1) {
  // try例外処理ブロック1
} catch (例外型2 引数2) {
  // try例外処理ブロック2
}
...
} catch (例外型N 引数N) {
  // try例外処理ブロックN
} finally {
  // finallyブロック
}

tryブロックは、例外が発生する可能性のある範囲を指定するブロックである。プログラム実行中に例外が発生し得る範囲をtryブロックとして指定すればよい。tryブロックには少なくとも1つ以上のcatchブロックが必要である。

catchブロックは、tryブロックで例外が発生したときに実行されるブロックである。catchブロックの例外型の引数には、tryブロックで発生した例外に関する情報が含まれている。tryブロックではさまざまな例外が発生する可能性があり、その例外の種類に応じて複数のcatchブロックを指定できる。

finallyブロックは任意であり、例外の発生有無に関係なく必ず実行されるブロックである。つまり、tryブロックが実行されると、例外が発生しても発生しなくても必ず実行されるブロックである。

通常、オブジェクトを生成し、必ずcloseしなければならない場合によく使用される。例えば、ファイルを開いて閉じる場合や、データベースを開いて必ず閉じる必要がある場合などがある。

package com.devkuma.tutorial.exception;

public class TryCatch {

    public static void main(String[] args) {

        try {
            int i = Integer.parseInt(args[0]);

            System.out.println("i=" + i);

        } catch (NumberFormatException e) {
            System.out.println("NumberFormatException");
            throw e;
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("ArrayIndexOutOfBoundsException");
            throw e;
        }
    }
}

改良されたtry~catchでの複数例外

JDK 1.7の拡張機能により、try~catchで複数の例外を受け取って処理できるようになった。 互いに異なる継承階層にある例外同士で可能である。例えば、NumberFormatExceptionとArrayIndexOutOfBoundsExceptionは同時に処理できる。

JDK 1.7以前は、以下のような方法で例外を処理していた。

...省略...
} catch (NumberFormatException e) {
    System.out.println("NumberFormatException");
    throw e;
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("ArrayIndexOutOfBoundsException");
    throw e;
}

JDK 1.7以降は、以下のような方法が可能になった。

...省略...
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
    System.out.println("Exception");
    throw e;
}

例外処理を渡す - throws

例外を処理する方法には、try~catch文でメソッド内で処理する方法と、発生した例外を呼び出し元のメソッドへ渡す方法がある。

このような例外処理方式は、1つのメソッドですべて処理する場合に便利であり、throwsキーワードを使用して例外を指定する。処理する例外が多い場合は、カンマ(,)で区切って列挙する。

public void method() throws 例外クラス[, 例外クラス...]

人為的な例外発生 - throw

JVMはJavaプログラムの実行中に例外が発生すると、自動的に該当する例外オブジェクトを発生させ、その後、例外を処理するためのcatchルーチンを探す。

例外はJVMによって実行中に発生することもあるが、ユーザープログラムで人為的に例外を発生させることもできる。Javaは例外を人為的に発生させるためにthrowキーワードを使用する。

throw 例外オブジェクト;

または

throw new 例外クラス(引数);

ユーザー定義例外クラス

ユーザーは、Javaで提供される例外クラス以外に、追加で自分自身のクラスを作成することもできる。ユーザー定義例外クラスを作るには、Exceptionクラスを継承して新しい例外クラスを作成すればよい。

参考