WisdomSoft - for your serial experiences.

8.2 例外のキャッチ

投げられた例外は try 文によって検出し、エラー情報の取得、ユーザーへの警告、ログの保存など、プログラムによって必要な回復処理を制御できます。

8.2.1 try文

例外が発生すると、強制的に制御を失いプログラムが終了しました。しかし、これは適切な終了方法とは思えません。エラーが発生したならば、それなりの終了処理やユーザーに対するエラー発生の通知などを行うべきでしょう。つまり、上品にプログラムを終了させましょうということです。

開発者は、例外のスローを受け取り例外処理を行うコードに制御を導くことができます。これを、例外をキャッチすると表現します。例外をキャッチするには try 文を使います。try 文はブロック内で発生した例外をキャッチする能力を持ちます。

try 文
try ブロック catch(例外パラメータ) ブロック

try キーワードの直後のブロックには、例外が発生する可能性がある文を記述します。このブロック内で例外がスローされた場合、処理はその時点で中断して catch ブロックに制御が強制的に移行します。感覚的には catch は例外を処理する専用のメソッドのようにも見えることから、catch のことを例外ハンドラと呼ぶこともあります。一般的なプログラムでは、次のように例外を捕捉します。

try {
	//例外が発生する可能性のあるプログラム
}
catch(例外型 識別子) {
	//例外に対する処理
}

例外型とは、例外が発生した時に生成された、例外の発生理由などの情報を格納したオブジェクトのことです。Java では、どこかで発生した実行時の不正な処理の原因を伝達するために、クラスのインスタンスを投げるのです。catch ブロックでは、このオブジェクトをパラメータとして受け取り、この情報を基に適切な処置を施します。例えば、8.1 例外の発生 コード1では ArithmeticException クラスがスローされていました。

発生した例外が catch で指定されている例外型と一致しなければ、catch ブロックは実行されません。例外の種類や原因によって処置が異なるのは当然のことでしょう。try 文に対し catch は複数指定することが可能なので、目的の例外処理ごとにブロックを記述することができます。catch で適切な処理を施しブロックから抜け出すと、プログラムの制御は try 文から抜け出します。

コード1
class Test {
	public static void main(String args[]) {
		try { int iValue = 10 / 0; }
		catch(ArithmeticException err) {
			System.out.println("算術処理で例外が発生しました");
		}
	}
}
実行結果
>java Test
算術処理で例外が発生しました

コード1は、8.1 例外の発生 コード1を改良したもので、0 の除算によって発生した算術例外をキャッチして処理しています。try ブロックに囲まれた式文で 0 による除算を行っていますが、これによって算術例外 ArithmeticException がスローされます。スローされた例外を catch で受け取り、制御が catch ブロックに移行していることが実行結果から確認することができます。

では、例外の種類はどのくらいあるのでしょうか。そもそも例外とは Throwable クラスをスーパークラスとしなければならないと定められています。例外として投げられたオブジェクトは、必ず Throwable クラスにワイドニング変換することができます。つまり、例外とは Throwable クラスを指すと思ってください。すべての例外はこのクラスを継承しています。

表1 Throwable クラス
コンストラクタ 解説
Throwable() 詳細メッセージに null を使用して、新規例外を構築します。
Throwable(String message) 指定された詳細メッセージを使用して、新規例外を構築します。
Throwable(String message, Throwable cause) 指定された詳細メッセージおよび原因を使用して新規例外を構築します。
Throwable(Throwable cause) 指定された原因を使用して新規例外を構築します。
メソッド
Throwable fillInStackTrace() 実行スタックトレースを埋め込みます。
Throwable getCause() 原因または null (原因が存在しないか未知の場合) を返します。
String getLocalizedMessage() 地域対応された記述を作成します。
String getMessage() 詳細メッセージ文字列を返します。
StackTraceElement[] getStackTrace() printStackTrace() により出力されたスタックトレース情報への、プログラム化されたアクセスを提供します。
Throwable initCause(Throwable cause) 「原因」を、指定された値に初期化します。
void printStackTrace() バックトレースを標準エラーストリームに出力します。
void printStackTrace(PrintStream s) バックトレースを指定された印刷ストリームに出力します。
void printStackTrace(PrintWriter s) バックトレースを指定されたプリントライターに出力します。
void setStackTrace(StackTraceElement[] stackTrace) getStackTrace() により返され、printStackTrace() および関連するメソッドにより出力される、スタックトレース要素を設定します。
String toString() この例外の短い記述を返します。

さらに例外は通常の例外と致命的なエラーに大きく分けられます。Throwable を継承し、通常の例外のスーパークラスとなるのが Exception クラス、致命的なエラーのスーパークラスとなるのが Error クラスです。Java のクラスライブラリで提供されているすべての例外は、これらのクラスのいずれかを継承しているでしょう。

私たち Java プログラマがキャッチするべき例外は常に Exception のサブクラスです。致命的なエラーである Error 型の例外は、通常はあってはならない異常な事態を表すため、これが発生した時点でアプリケーション・プログラムにできることは尽きています。Error の発生とは、仮想マシンそのものが壊れていたり、バイトコードが不正なものだったりという、アプリケーションでは手に負えないものばかりです。詳しくは Java API 仕様から Error のサブクラスを調べてみると良いでしょう。

では、それ以外の通常の例外はどのようなものがあるのでしょうか。java.lang パッケージで宣言されている代表的な例外は、表2のようなものがあります。

表2 一般的な例外
例外クラス 発生理由
ArithmeticException 算術計算で例外的条件が発生した(0 の除算など)
ArrayIndexOutOfBoundsException 不正なインデックスを使って配列がアクセスされた
ArrayStoreException 不正な型のオブジェクトをオブジェクトの配列に格納しようとした
ClassCastException あるオブジェクトを継承関係にないクラスにキャストしようとした
IndexOutOfBoundsException ある種のインデックス (配列、文字列、ベクタなど) が範囲外である
NegativeArraySizeException 負のサイズを持った配列をアプリケーションが作成しようとした
NullPointerException null を参照した

これらは数多くある例外の一部ですが、実行時に発生する可能性の高い一般的な例外です。

コード2
class Test {
	public static void main(String args[]) {
		try {
			if (args[0].equals("/n")) {
				String str = null;
				int i = str.length();
			}
			else if (args[0].equals("/c")) {
				Test obj = (Test)((Object)args);
			}
			else if (args[0].equals("/z")) {
				int i = 10 / 0;
			}
			System.out.println("正常に try ブロックを終了します");
		}
		catch(ArithmeticException err) {
			System.out.println("不正な算術演算です");
		}
		catch(ArrayIndexOutOfBoundsException err) {
			System.out.println("配列に不正なインデックスでアクセスしました");
		}
		catch(NullPointerException err) {
			System.out.println("null を参照しました");
		}
		catch(ClassCastException err) {
			System.out.println("不正なクラスのキャストです");
		}
	}
}
実行結果
>java Test /n
null を参照しました

>java Test /c
不正なクラスのキャストです

>java Test /z
不正な算術演算です

>java Test
配列に不正なインデックスでアクセスしました

>java Test "Blue Blue Glass Moon"
正常に try ブロックを終了します

コード2では、try ブロックで発生する可能性があるすべての例外を各 catch ブロックでキャッチし、処理しています。try 文において catch ブロックは複数指定することができるため、例外型に応じた処理を実行することができるのです。例外がスローされると、プログラムは例外型に一致する try 文の catch を検索するのです。このとき、例外を処理する catch のことを例外を取り扱うと表現します。

特定の catch が例外を取り扱うために、必ずその型と一致しなければならないというわけではありません。スローされた例外が catch の型にワイドニング変換可能であれば、その catch は例外を取り扱うことができます。発生した例外のスーパークラスの型で取り扱えば、一元化して例外の処理を記述することができるでしょう。 

コード3
class Test {
	public static void main(String args[]) {
		try {
			if (args[0].equals("/n")) {
				String str = null;
				int i = str.length();
			}
			else if (args[0].equals("/c")) {
				Test obj = (Test)((Object)args);
			}
			else if (args[0].equals("/z")) {
				int i = 10 / 0;
			}
			System.out.println("正常に try ブロックを終了します");
		}
		catch(Exception err) {
			System.out.println("例外が発生しました : " + err.toString());
		}
	}
}
実行結果
>java Test
例外が発生しました : java.lang.ArrayIndexOutOfBoundsException

>java Test /n
例外が発生しました : java.lang.NullPointerException

>java Test /c
例外が発生しました : java.lang.ClassCastException: [Ljava.lang.String;

>java Test /z
例外が発生しました : java.lang.ArithmeticException: / by zero

コード3は、コード2を改良して、例外を取り扱う catch を一元化しています。この try 文の catch は Exception 型の例外をキャッチします。Exception は一般的な例外のスーパークラスとなっているため、アプリケーションが処理するべき一般的な例外の多くはこれで取り扱うことができます。例外がスローされると、例外型に一致する catch を検索し、それが存在しなければ例外型のスーパークラスを取り扱う catch を検索します。このプログラムの try 文で発生する例外は、すべて catch で取り扱っています。もし、null 参照による例外だけ異なる処置をしたい場合は、NullPointerException を受け取る catch をさらに追加すればよいでしょう。

8.2.2 finally ブロック

try 文を実行した結果、例外が発生するしないに問わず、何らかの処理を最終的に実行したい場合は finally というブロックを定義します。finally は try 文の一部で catch に続いて記述するブロックです。try 文は正常に try ブロックが終了しても、例外がスローされても必ず finally ブロックを実行します。そのため、何らかのリソースを破棄する処理などを記述する時に利用することができるでしょう。

try 文
try ブロック catch群 finally ブロック

finally が指定されている try 文では、例外をキャッチしても、スローされた例外を取り扱える catch がなくても、正常に終了されても、いずれも必ず finally が実行されることが保証されます。

コード4
class Test {
	public static void main(String args[]) {
		try {
			if (args[0].equals("/n")) {
				String str = null;
				int i = str.length();
			}
			else if (args[0].equals("/c")) {
				Test obj = (Test)((Object)args);
			}
			System.out.println("正常に try ブロックを終了します");
		}
		catch(Exception err) {
			System.out.println("例外が発生しました : " + err.toString());
		}
		finally {
			System.out.println("Finally ブロックが実行されました");
		}
		
	}
}
実行結果
>java Test
例外が発生しました : java.lang.ArrayIndexOutOfBoundsException
Finally ブロックが実行されました

>java Test /c
例外が発生しました : java.lang.ClassCastException: [Ljava.lang.String;
Finally ブロックが実行されました

>java Test Kitty
正常に try ブロックを終了します
Finally ブロックが実行されました

コード4の try 文は finally が指定されています。そのため、try 文が実行されると最終的に必ず finally ブロックが実行されます。実行結果を見れば、例外がスローされても、されなくても finally ブロックが実行されていることを確認することができます。

finally ブロックが指定されている場合、catch を省略することが可能になります。try 文は必ず catch または finally ブロックを指定しなければならないと定められているため、finally を指定すれば catch を省略しても問題はありません。

コード5
class Test {
	public static void main(String args[]) {
		try {
			System.out.println("try ブロックです");
			System.out.println("args[0] = " + args[0]);
			System.out.println("try ブロックを正常に終了します");
		}
		finally {
			System.out.println("finally ブロックです");
		}
		System.out.println("try 文を終了しました");
	}
}
実行結果
>java Test "Blue Blue Glass Moon"
try ブロックです
args[0] = Blue Blue Glass Moon
try ブロックを正常に終了します
finally ブロックです
try 文を終了しました

>java Test
try ブロックです
finally ブロックです
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
        at Test.main(Test.java:5)

コード5の try 文は catch ブロックを省略しています。その代わりに finally を指定しているため問題はありません。try ブロックで例外がスローされなければ、正常に try ブロックが終了し、finally ブロックが実行された後、try 文が終了します。引数を指定して実行すると、try 文が正常に終了していることが確認できます。

一方 try 文で例外がスローされると、例外を取り扱う catch が存在しないため、即座に finally が実行されて強制終了します。例外を処理できる catch があれば try 文を正常に終了することができますが、この try 文は catch が存在しないため、finally ブロックを実行した後、例外が仮想マシンにスローされ強制終了されてしまうのです。