例外処理

サンプルプログラム

Javaのプログラムの中には、文法上の誤りはないものの、プログラムの動作の過程で問題が発生し、動作が止まってしまうものがあります。たとえば、以下のようなものです。

SampleEx701.java
package exday7;

public class SampleEx701 {

	public static void main(String[] args) {
		int a = 0;
		int b = 5;
		System.out.println(b / a);
	}

}
実行結果
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at exday7.SampleEx701.main(SampleEx701.java:8)

このようなメッセージが出た理由は、0による割り算が行われたためです。8行目に存在する「b / a」という処理自体は、文法上の誤りはありません。しかし、aが0であったことから、このようなメッセージが発せられ、プログラムが強制的に終了させられてしまったのです。

例外と例外処理

このように、プログラムが、何らかの以上によって終了することを、例外(れいがい)と言います。これに対し、例外が発生したときにする何らかの処理のことを例外処理(れいがいしょり)と言います。

表7-1.Javaの例外処理の種類
処理方法 内容
try~catch 例外を、tryの{}内で起こった例外を、catch以下で処理します。
throw 発生した例外を、他のクラスにゆだねます。

以下、それぞれのケースについて説明していきます。

try/catchによる例外処理

サンプルプログラム

まずは、try/catchによる例外処理を見てみましょう。以下のプログラムを実行してみてください

SampleEx702.java
package exday7;

public class SampleEx702 {

	public static void main(String[] args) {
		try{
			for(int i = 3; i >= 0; i--){
				int a = i;
				int b = 5;
				System.out.print(b + " / " + a + " = ");
				System.out.println(b / a);
			}
		}catch(ArithmeticException e){
			System.out.println();
			System.out.println("0による割り算発生");
		}finally{
			System.out.println("終了");
		}
	}

}
実行結果
5 / 3 = 1
5 / 2 = 2
5 / 1 = 5
5 / 0 =
0による割り算発生
終了

このサンプルも、最初のものと同様に、プログラム中で0割り算による例外が発生しています。ループ内で、i=0のときに、11行目の処理で例外が発生します。しかし、プログラムは異常終了せず、例外が発生したことを告げるメッセージを出して終わっています。いったいどうしてでしょう?

try~catchは、{}内で例外が発生した場合、それに対処する処理を記述するためのものです。書式は以下の通りになります。

try~catchの書式
try{
    (処理①)
}catch((例外クラス) 変数){
    (処理②)
}finally{
    (処理③)
}

処理①の中で、エラーが出た場合、例外処理である、処理②が実行されます。また、処理③は、例外が発生する、しないに関係なく実行されます。なので、例外が発生する場合、処理①→処理②→処理③の順に処理が実行され、発生しない場合には、処理①→処理③となります。したがって、SampleEx702.javaの処理の流れは以下のようになります。(図7-1.)

図7-1.try/catchによる例外処理
try/catchによる例外処理

例外クラス

ここで出てくる、ArithmeticExceptionとは、例外クラスの一つで、0割り算による例外が発生したときにインスタンスが生成されます。例外クラスは、このほかにもさまざまな種類がありますが、いずれも、Exceptionクラスを継承しています。

throws

サンプルプログラム

例外を処理するほうほうとして、try~catch以外によるもの以外に、throwsを用いる方法があります。以下のサンプルを実行してみてください。

SampleEx703.java
package exday7;

public class SampleEx703 {

	public static void main(String[] args) {
		try{
			for(int i = 0; i<= 5; i++){
				int a = getNum(i);
				int b = 5;
				System.out.print(a + " / " + b + " = ");
				System.out.println(calc(a,b));
			}
		}catch(ArithmeticException e){
			System.out.println();
			System.out.println("0による割り算発生");
		}catch(ArrayIndexOutOfBoundsException e){
			System.out.println("配列の範囲外にアクセスしました");
		}finally{
			System.out.println("終了");
		}
	}
	//
	private static int calc(int a,int b) throws ArithmeticException{
		return a / b;
	}
	//	範囲外に出たときの処理
	public static int getNum(int index) throws ArrayIndexOutOfBoundsException{
		int[] num = { 1, 2, 3, 4 };
		return num[index];
	}

}
実行結果
1 / 5 = 0
2 / 5 = 0
3 / 5 = 0
4 / 5 = 0
配列の範囲外にアクセスしました
終了

6行目から13行目のtryの中で、例外が起こった場合、catchに飛びます。このサンプルのようにcatchは複数定義できます。発生した例外の種類によって、処理を分岐させることができます。

23行目および27行目で出てくる、throwsキーワードは、メソッド内で発生した例外を、外部にその処理をゆだねるためのものです。書式は以下の通りです。

throwsキーワードの書式
メソッド名 (引数型 引数名) throws 例外クラス型,…

23行目のcalcメソッド、および27行目のgetNumメソッドは、それぞれ処理の内部で、ArithmeticExceptionおよび、ArrayIndexOutOfBoundsExceptionを発生させる可能性があります。

しかし、ここにthrowsキーワードをつけることにより、その例外処理をこれらメソッドの呼び出しもとにゆだねることができます。SampleEx703.javaでは、29行目で、ArrayIndexOutOfBoundsExceptionが発生するため、その例外は、throwsによって、mainの8行目の例外として処理されます。(図7-2.)

図7-2.throwsによる例外処理
throwsによる例外処理

例学の種類と、独自例外の作成

例外関連クラスの種類

例外クラスが、Exceptionクラスを継承したクラスであることはすでに述べたとおりです。この継承関係をさらに詳しくすると、以下の図の通りになります。

Exceptionクラスは、Throwableを継承し、さらに例外の中にも、Exceptionを継承したRuntimeExceptionがあります。また、Throwableを継承している者にも、Errorクラスがあり、それらは以下のような役割があります。(表7-2、図7-3)

図7-3.例外関連クラスの継承関係
例外関連クラスの継承関係
表7-2.Javaの例外処理に関連するクラス
クラス名 働き 主なサブクラス
Throwable 全てのエラーおよび例外のスーパークラスです。通常、このクラスをプログラム内で直接使用することはありません。
Error 本来アプリケーションで発生してはならない重大なエラーを表現しています。プログラム内で拡張したり、サブクラスを定義することはありません。 NoClassDefFoundError
OutOfMemoryError
Exception 一般的な例外を扱うクラスです。Javaで発生する多くの例外は、このクラスを継承して使用されます。このクラスを継承し、独自のクラスを作成することがあります。このクラスを継承している例外のことを、検査例外(チェック例外)と言います。 ClassNotFoundException
CloneNotSupportedException
DataFormatException
FileNotFoundException
RuntimeException Exceptionのサブクラスで、一般的にビルド時にエラーとして検出しきれないプログラムのミスにより、実行時に発生する例外クラスです。
必要に応じ、このクラスを継承した独自の例外を作成するこがあります。
ClassCastException
IllegalArgumentException
IndexOutOfBoundsException
NullPointerException

検査例外

表7-2.で説明したとおり、RuntimeExceptionを継承した例外クラスのことを、検査例外(けんされいがい)と言います。それ以外の例外と、検査例外は、どこが違うのでしょうか?

検査例外が発生する可能性のある処理を記述する場合には、必ず例外処理を記述しなくてはならないという決まりがあります。逆に、検査例外ではない例外、つまり、RuntimeExceptionを継承したような例外の場合、必ずしも例外処理を記述する必要はありません。

サンプルプログラム

表7-2.からわかるとおり、プログラマーは、Exceptionクラス、およびRuntimeExceptionクラスを継承した独自の例外を作成することが可能です。以下、実際に例外を作成してみましょう。

SampleException.java
package exday7;

//	簡単な例外クラスのサンプル
public class SampleException extends Exception{
	//	シリアルバージョン番号
	private static final long serialVersionUID = 1L;

	//	コンストラクタ
	public SampleException(String message) {
		super(message);
	}
}
SampleEx704.java
package exday7;

public class SampleEx704 {

	public static void main(String[] args) {
		// TODO 自動生成されたメソッド・スタブ
		try{
			//	故意に例外を発生させる
			throw new SampleException("自作の例外のサンプル");
		}catch(SampleException e){
			e.printStackTrace();
		}
	}

}

実行結果
exday7.SampleException: 自作の例外のサンプル
               at exday7.SampleEx704.main(SampleEx704.java:9)

クラス、SampleExceptionは、Exceptionクラスを継承しているのます。これにより、このクラスは、例外クラスとして利用することができます。

throw

では、ユーザーが独自に作成した例外を発生させるには、どのようにすればよいのでしょうか?それを可能にしているのが、SampleEx704.javaの9行目に出ている、throwです。throwは、ユーザー定義されているものに限らず、例外を発生させることができます。書式は以下のようになります。

throwで例外を発生させる
throw new 例外クラス名(コンストラクタの引数,…);

SampleExceptionクラスは、コンストラクタでとしてString型の引数を一つ必要としています。これによって発生した例外が、SampleEx704.javaの10行目でキャッチされます。

例外クラスの仕組み

では次に、例外クラスの詳しい仕組みを見ていきましょう。SampleException.javaの6行目を見てください。変数serialVersionUIDによって、シリアルバージョン番号が定義されています。例外クラスは、義務ではありませんが、このようにシリアルバージョン番号をつけることが推奨されています。

また、コンストラクタの中で、引数messageを、super()を使ってスーパークラスに渡しています。このメッセージが、例外発生時のメッセージになるわけです。それが出力されるのが、SampleEx704.javaの11行目です。

printStackTrace()メソッドを用いれば、そのメッセージがコンソールに出力される仕組みになっています。これにより、ユーザー独自の例外を使用することができるわけです。

練習問題 : 問題7.