独自例外クラスの作成

Revised: Mar./21st/2002: Since: Jan./27th/2002

適当な例外クラスを継承して、独自定義の例外クラスを作ることも出来ます。

まず、全ての例外クラスは java.lang.Throwable のサブクラスであり、 throw 可能で、メソッドの throws リストに加えることも出来ます。また、スーパークラスに選んだ例外クラスによっては、そのクラスで定義された特殊なメソッドが使えます。

独自の例外クラスを作る理由

Java コアパッケージに用意されている例外クラス型は多岐に及びます。独自に作る理由は、これらの定義済みの例外クラス型オブジェクトをそのままスローしたくないからです。

例えば、ファイルが見つからないために発生する例外 java.io.FileNotFoundException の場合、データの保存先をファイルからデータベースに変えたら、java.sql.SQLException に変更になるでしょう。メソッドが投げるチェック例外が変更を受けると、そのメソッドを呼ぶメソッドや、そのサブクラスのオーバーライドメソッドも修正してコンパイル/パッケージングする必要があります。また、IOやデータベース接続のような低レベルな例外を受け取っても、呼び出し元が回復処理を実装することは困難です。

このようなとき、元々発生した例外をキャッチして、別の例外を生成してスローしなおすということが行われます。このとき新しく作る例外オブジェクトは、そのメソッドで行いたいこと=業務的に意味のある例外型であるべきです。既存の例外クラスに存在しない場合は、自分で作成することになります。

チェック例外か非チェック例外か

自分が作成する例外クラスのスーパークラスを選択するのに重要なことは、その例外をチェック例外と非チェック例外のどちらにするかです。

コンパイル時の例外検査を通るチェック例外と例外検査を通らない非チェック例外の何れにするかは、システム全体の例外処理方針を決定する上でとても重要です。例外処理は全てのクラスで、全てのプログラマが触ることになるので影響も大きく、途中で方針変更しづらい部分です。

非チェック例外にするためには、RuntimeException を継承します。しかし、通常は Exception クラスから継承してチェック例外にすることが多いでしょう。

こうするものだという一般的な方針はありません。一般的には、回復処理が可能であればチェック例外とし、キャッチしてもログを吐くほかにやることがないのであれば非チェック例外とすることが多いようです。

サンプル

具体的な例を作ってから、それに合わせて例外クラスを定義してみます。

次の例は、ユーザ名の配列を保持するクラスです。インスタンス化のときにコンストラクタにユーザ名の配列を与えます。特定のユーザを表す要素のインデックスを、当該ユーザの ID として、 ID からユーザ名を、ユーザ名から ID を得ることが出来ます。

class UserList {
	private String[] users;
	// ユーザの配列を代入
	void setUsers(String[] args) {
		// 配列の参照の値の代入
		users=args;
	}
	// UID からユーザ名を取得
	String getUser(int uid) {
		return users[uid];
	}
	// ユーザ名から UID を取得
	int getUser(String user) {
		for (int i=0; i<users.length; i++) {
			if (user.equals(users[i])) {
				return i;
			}
		}
		// 整数値を返す必要があるので、
		// ここではデフォルトは 0 とした。
		return 0;
	}
}

このクラスは文字列型の配列を保持し、メソッドにはそのゲッター (getXyz()) とセッター (setXyz()) が定義されています。このクラスを使うコントロールクラスを次のように定義してみます。

UserTest.java:

class UserTest {
	public static void main(String[] args) {
		// インスタンス化
		UserList obj = new UserList();
		// ユーザ配列のセット
		obj.setUsers(args);
		System.out.print("sugai -> ");
		System.out.println(obj.getUser("sugai"));
		System.out.print("0 -> ");
		System.out.println(obj.getUser(0));
	}
}

上で挙げた UserList クラスはこのクラスと同じファイルに記述しても構いませんし、分けても構いません。但し、同じディレクトリに存在していることが必要です。ファイルに保存できたら実行してみます。

実行例:

C:\Java\Excep>javac UserTest.java
C:\Java\Excep>java UserTest root team01 team02 sugai system admin
sugai -> 3
0 -> root

サンプル

例外クラスの定義

ここで定義した UserList クラスにはプリミティブな問題が散見されます。

  1. ユーザが見付からなければデフォルト値 "0" を返しているのは乱暴
  2. "uid" の適正値がアサインされない場合の処理がない
  3. ユーザリストが存在しない場合が考慮されていない

このような問題を解決する為に、ここでは独自の例外クラスを定義してみます。

UserNotAuthorizedException
ユーザが見付からない場合に発生
UidOutOfBoundException
指定された UID が適切な範囲内ではない場合に発生
NullUsersException
ユーザがセットされる前にユーザが要求された場合に発生

このように、独自の定義で例外を作る場合は、末尾を Exception にするのが命名慣例 (naming convention) です。

これらの例外クラスをこれから作りますが、スーパークラス UsersException を作って、それから派生させることにします。この例外クラスは Exception クラスの派生として作ります。

// UserList クラスが発生する例外のスーパークラス
class UsersException extends Exception {
	public UsersException(String str) {
		super(str);
	}
}
class UserNotAuthorizedException extends UsersException {
	// UID
	private int id;
	// ユーザ名
	private String name;
	// コンストラクタ
	public UserNotAuthorizedException() {
		super("このユーザは認証されません。");
	}
	public void setUid(int i) {
		id = i;
	}
	public void setName(String user) {
		name = user;
	}
	public String getName() {
		return name;
	}
	public int getId() {
		return id;
	}
}
class UidOutOfBoundException extends UsersException {
	// UID の上限
	private int hid;
	// コンストラクタ(引数は UID の上限)
	public UidOutOfBoundException(int i) {
		super("UID の範囲は" + 0 + "から" + i + "です。");
		hid = i;
	}
	public int getHid() {
		return hid;
	}
}
class NullUsersException extends UsersException {
	// コンストラクタ
	NullUsersException() {
		super("ユーザが定義されていません。");
	}
}

これらの例外クラスをコンパイルしてバイトコード(*.class ファイル)を用意しておきます。

C:\Java\Excep>javac UsersException.java
C:\Java\Excep>

例外クラスの利用

では、準備が整いましたので、 UserList クラスをこれらの例外を throw するように編集します。

UserList.java:

class UserList {
	private String[] users;
	void setUsers(String[] args) {
		// 配列の参照の値の代入
		users = args;
	}
	String[] getUsers() throws NullUsersException {
		if (users.equals(null)) {
			throw new NullUsersException();
		}
		return users;
	}
	String getUser(int uid) throws UsersException {
		if (users.equals(null)) {
			throw new NullUsersException();
		} else if (uid < 0 || uid >= users.length) {
			UidOutOfBoundException excep
			    = new UidOutOfBoundException(users.length - 1);
			throw excep;
		}
		return users[uid];
	}
	int getUser(String user) throws UsersException {
		int uid = 0;
		if (users.length == 0) {
			throw new NullUsersException();
		} else {
			for (int i = 0; i < users.length; i++) {
				if (user.equals(users[i])) {
					uid = i;
					break;
				} else if (i == users.length - 1) {
					UserNotAuthorizedException excep
					    = new UserNotAuthorizedException();
					excep.setName(user);
					throw excep;
				}
			}
		}
		return uid;
	}
}

UsersList クラスの private ではないインタフェース部分を編集したので、このクラスをインスタンス化するコントロールクラスの方も編集します。

class UserTest {
	public static void main(String[] args) {
		UserList obj = new UserList();
		obj.setUsers(args);
		try {
			System.out.print("sugai -> ");
			System.out.println(obj.getUser("sugai"));
			System.out.print("10 -> ");
			System.out.println(obj.getUser(10));
		} catch (UsersException e) {
			System.out.println("");
			System.out.println(e);
			return;
		}
	}
}

コンパイルして実行します。コマンドライン引数がユーザ名の配列になります。引数を与えないで実行した場合は、ユーザが定義されていないので、例外 NullUsersException が発生するはずです。

C:\Java\Excep>javac UserTest.java
C:\Java\Excep>java UserTest
sugai ->
NullUsersException: ユーザが定義されていません。
C:\Java\Excep>

引数を与えても、対応するユーザが存在しなければ UserNotAuthorizedException が発生するはずです。

C:\Java\Excep>java UserTest root team01 team02
sugai ->
UserNotAuthorizedException: このユーザは認証されません。
C:\Java\Excep>

さらに、配列のインデックスを UID としてユーザ名をゲットできるはずですが、 UID が配列の要素数を超えたり、負数だったりすれば、 UidOutOfBoundException が発生するはずです。

C:\Java\Excep>java UserTest root team01 team02 sugai
sugai -> 3
10 ->
UidOutOfBoundException: UID の範囲は0から3です。
C:\Java\Excep>


Copyright © 2001 SUGAI, Manabu. All Rights Reserved.