WisdomSoft - for your serial experiences.

8.4 チェック例外

RuntimeException 例外クラスを継承する一部のシステム的な例外を除き、スロー宣言でメソッドが発生させる可能性のある例外を宣言しなければなりません。この仕組みをチェック例外と呼びます。

8.4.1 スロー宣言

特定のブロック内で発生した例外をその場で取り扱うのならば、それは内部仕様の問題です。クラスの利用者がその事実を知る必要はありません。しかし、メソッドが何らかの例外をスローし、それを取り扱わないのであれば、メソッドを呼び出すクラス利用者がスローされた例外を取り扱う必要があります。ところが、必ずしもメソッドがスローした例外を、クラスの利用者がキャッチする保証はありません。そのため、Java コンパイラは明示的にスローされた例外がキャッチされない場合はエラーを出します。

コード1
class Test {
	public static void main(String args[]) {
		ThrowException();
	}
	private static void ThrowException() {
		throw new Exception();
	}
}
実行結果
>javac Test.java
Test.java:6: 例外 java.lang.Exception は報告されません。スローするにはキャッチま
たは、スロー宣言をしなければなりません。
                throw new Exception();
                ^
エラー 1 個

コード1は、単純に Exception 例外を発生させるだけの ThrowException() メソッドを宣言しています。main() メソッドからこのメソッドを呼び出すことによって例外がスローされるはずですが、このプログラムはコンパイルすることができません。一見すると構文上のミスは無いように見られますが、スローされる可能性がある例外が確実にキャッチされる保証がないという理由からエラーが発生します。

そこで、Java 言語ではメソッドが何らかの例外をスローする可能性があることを通知するためにスロー宣言をメソッドに指定することができます。スロー宣言は、メソッド宣言の仮パラメータリストの直後に throws キーワードと例外型を指定します。これは、次のようになります。

スロー宣言
メソッド修飾子 戻り値型 メソッド名(仮パラメータリスト) throws 例外型 { メソッド本体 }

コンストラクタでも、同様にスロー宣言を記述することができます。スロー宣言されたメソッドは、指定した例外型がスローされる可能性があることをアピールすることができます。スローされる可能性がある例外が複数存在する場合はコンマ , で区切って複数指定することができます。

throws 例外型1 , 例外型2 , ...

スロー宣言で指定できる型は、必ず Throwable かそのサブクラスでなければなりません。これで、メソッドはスロー宣言で指定した例外をメソッドの呼び出し元にスローすることができるようになります。メソッドの呼び出し側もまた、スロー宣言によってメソッドがスローする可能性がある例外を知ることができるようになります。

コード2
class Test {
	public static void main(String args[]) {
		ThrowException();
	}
	private static void ThrowException() throws Exception {
		throw new Exception();
	}
}
実行結果
>javac Test.java
Test.java:3: 例外 java.lang.Exception は報告されません。スローするにはキャッチま
たは、スロー宣言をしなければなりません。
                ThrowException();
                ^
エラー 1 個

コード2 コード1を改良して ThrowException() メソッドにスロー宣言を加えています。そのため、ThrowException() メソッドは Exception 型の例外を呼び出し元にスローすることができます。ところが、このプログラムもまたコンパイル・エラーとなってしまいます。それは、ThrowException() を呼び出している main() メソッドが Exception 例外を取り扱っていないため、さらにその呼び出し元に Exception 例外をスローする可能性があるためです。

例外が catch によって取り扱われない場合、メソッドの呼び出し元に対して次々とスローされていきます。最終的には、確実に main() メソッドにたどり着き、それでも処理されない場合は仮想マシンへと流れていきます。コード2をコンパイルするには、Exception 例外をキャッチできる try 文の中でThrowException() を呼び出すか、main() メソッドにスロー宣言を追加するかのいずれかの方法が考えられます。

このように、コンパイル時に例外が発生する可能性を分析され、その取り扱いを調べられることをチェック例外と呼びます。コンパイラは、チェック例外を取り扱う catch が存在するかを調べ、それが存在しない場合はメソッドがスロー宣言しているかどうかを確認します。これらの条件が満たされない場合は、チェック例外が正しく取り扱われないためエラーを発生させるのです。

ただし、どのような場所でも発生する可能性のある例外は、それを補足することが難しく、それ自体に大きな意味を持たないため、チェックされない例外クラスとして用意されています。チェックされない例外クラスとは Exception クラスを継承する RuntimeException クラスと Error クラスです。これらのクラス、またはそのサブクラスはコンパイル時にチェックされることはありません。

RuntimeException は実行時例外を表すクラスで、null 参照や不正な算術、不正なインデックスへのアクセスなどの例外がこれを継承しています。これらの例外はプログラムの構造上の欠陥などからあらゆる場所で発生する可能性があります。これらをすべてチェックしていては、プログラマに余計な負担をかけてしまうだけなのです。

また、Error クラスがチェックされない理由は、以前にも説明したように、この例外がスローされる時点ですでに回復不可能な状態であると考えられるため、この例外をチェックすることに意味はないのです。

8.4.2 例外の実用例

ここで、例外を実践でどのように活用するのか、実用例を示したいと思います。多くのプログラマや納期に追われるエンジニアは、いち早く目的のプログラムを完成させクライアントに提供しなければならないという焦りから、エラー処理や再利用性を無視してしまうことがあります。しかし、何らかの問題が発生した時に、正しくプログラムを終了したり、または復帰可能な範囲で復帰することは重要です。問題の原因をユーザーに通知することで、サポートもやりやすくなるでしょう。

ここでは、RGB 形式の色情報を表す Color クラスを例にプログラムを組みます。Color クラスは各色要素 r、g、b を表す読み取り専用フィールドを提供するものとします。これらのフィールドを初期化するために、コンストラクタでは int 型の r、g、b を表す値をパラメータから取得します。ただし、r、g、b 各色要素は 0 ~ 255 までしか表現してはならないとします。

Color クラスの利用者は、コンストラクタの呼び出しで、各要素に対して負数や 255 以上の値を指定する可能性があります。不正な値が引数で渡された場合は、例外の出番というわけです。

コード3
class Color {
	private class ColorValueException extends Exception {
		public ColorValueException(String param) {
			super("RGB 要素 " + param + " の値が不正です\n\t" + 
				"r = " + r + " : g = " + g + " : b = " + b
			);
		}
	}

	public final int r , g , b;
	Color(int r , int g , int b) throws Exception {
		this.r = r;
		this.g = g;
		this.b = b;

		if (r > 0xFF || r < 0) throw new ColorValueException("r");
		else if (g > 0xFF || g < 0) throw new ColorValueException("g");
		else if (b > 0xFF || b < 0) throw new ColorValueException("b");
		
	}
}

class Test {
	public static void main(String args[]) {
		try { Color color = new Color(0xFF , 0 , -50); }
		catch(Exception err) { System.out.println(err); }
	}
}
実行結果
>java Test
Color$ColorValueException: RGB 要素 b の値が不正です
        r = 255 : g = 0 : b = -50

コード3の Color クラスでは、コンストラクタに RGB 各要素の値を指定してインスタンス化します。しかし、これらは int 型として指定することができるため 0 ~ 255 以外の値を渡される可能性もあるのです。コンストラクタに不正な値が渡された場合は、Exception 例外が発生するようにしましょう。

このプログラムはさらにちょっとしたテクニックが使われています。各要素の例外ごとに同じメッセージを指定するのは面倒なので、この Color クラスの例外に特化した新しい例外 ColorValueException を内部クラスとして作成しています。このクラスは、Color クラスの要素に不正な値が指定されたことを表す例外クラスです。ただし、このクラスは Color クラスに特化した例外なので、公開する必要がないと考えられるため、設計上の理由から内部クラスが妥当であると考えられます。ColorValueException クラスは Exception を継承しているため、外部に対しては Exception としてスローします。このように、例外クラスを継承して特定の例外ケースに特化させることができます。