1.オリジナルの非チェック例外を作成する

※この話題は研修時間の都合によりスキップすることがあります。

Exceptionクラスまたはそのサブクラスを継承したオリジナルの例外を作成することができます。

11.カプセル化のところで使ったカジノゲームの残高の例で説明をしてみます。

以下のサンプルプログラムを見てください。OriginalExceptionという名前で非チェック例外のオリジナル例外クラスを作成してみます。RuntimeExceptionを継承すればいいですね。記述は以下のようになります。

class OriginalException extends RuntimeException

RuntimeExceptionクラスのサブクラスでOriginalExceptionクラスを宣言しました。コンストラクタはスーパークラスに丸投げしていますが、クラスの階層を上がっていってThrowableクラスまで行ってそこで詳細メッセージを初期化するようになっています。

次に、Player2クラスを作ります。

以前と同様にPlayer2は残高を1000ポイントを持っています。そして残高を超えて引き出そうとすると例外を発生させ、「残高不足です」と表示させたいとします。

このとき全く新規にPlayer2を作ってしまうと、Playerクラスとの関係が不明確になります。とはいえ以前のPlayerクラスを書き換えるというのは良くない選択肢です。なぜなら、Playerクラスを使っているクラスが存在する(可能性がある)からです。繰り返しになりますが、既に動いているクラスに手を加えることは極力避けましょう。機能変更や機能追加をするときの武器として継承を使いましょう。この考え方を「開放/閉鎖原則」といいます。(図14.3参照)

というわけでPlayer2クラスです。

注目していただきたいのは、OriginalExceptionのコンストラクタ呼び出しの実引数に文字列を渡しているところです。

トラブル報告書には、状況や原因が書かれていないといけません。それをしているのがこのコンストラクタなのです。この文字列がどのような実行結果として現れるかを後で確認してみて下さい。

また、

throw e;

という記述でOriginalExceptionのインスタンスを呼び出し元のメインメソッドに投げます。

最後は、メインメソッドの処理です。最初に200ポイント引き出して、次に1000ポイントを引き出そうとします。

<実行結果>

現在の残高です:800
Exception in thread "main" chap14.OriginalException: 残高不足です
at chap14.Player2.withdraw(Player2.java:10)
at chap14.Example10.main(Example10.java:8)

コンストラクタ呼び出しの実引数に渡した文字列はここでメッセージとして使われます。

最後にメインメソッドで例外が発生し、キャッチされることなく処理を中断しています。

下図14.3でここまでを振り返ってください。

開放/閉鎖原則の例
図14.3 開放/閉鎖原則の例

2.オリジナルのチェック例外を作成する

※この話題は研修時間の都合によりスキップすることがあります。

OriginalExceptionクラスは非チェック例外でした。では、チェック例外に変えてみると、どのようなことが起こるでしょうか?

といっても以下のようにExceptionクラス(RuntimeExceptionクラスではなく)を継承するだけであとは同じです。

class OriginalException2 extends Exception

以下のOriginalException2クラスを見てください。

そうするとPlayer3クラスはどのように変化するでしょうか?

以下のようにtry catchを付ける必要が出てきます。

あるいは、try catchを付けずにメソッドの宣言を以下のように変えても良いです。

public void withdraw(int amount) throws OriginalException2

このメソッド宣言の意味は、OriginalException2という例外を投げる可能性のあるメソッドだということを示しているのでした。ただし、Javaでは、オーバーライドされるメソッドが宣言するすべての例外を、オーバーライドするメソッドでも宣言する必要があります。つまり、throws節も含めてオーバーライドしなければいけないというルールなので、この場合はwithdrawのオーバーライドにはなりません。もしも、これを書かないと例外の発生源であるこのwithdrawメソッド内でtry-catchしなけばならないというのは前述のとおりです。

<実行結果>

現在の残高です:800
chap14.OriginalException2: 残高不足です

例外処理はトラブルが起こったことの報告でした。会社のトラブルの報告でも、直接対応した社員から上司に報告を上げることがありますね。同じように、Javaでは例外の報告を使用者に報告するのが基本原則です。例外を発生源で握りつぶすのではなく。

つまり、ここまでをまとめると、

例外のインスタンスをthrowした後の処理は2通りあります。

  • try~catch文で囲んでその場で処理する
  • メソッドの呼び出し側に投げて処理を任せる

そして、②の場合はメソッドの記述が以下のように変化しました。

<構文>

戻り値 メソッド名(引数) throws 例外の型 {  
  例外を投げる可能性のあるメソッドの内容
}

3.極力標準APIに備わっている例外クラスを使う

なお、今回は説明のため、あえて自分でオリジナルな例外クラスを作成しましたが、本当は極力標準APIに備わっている例外クラスを使うべきです。なぜなら、標準APIの例外クラスならば、Javaプログラマーの間でどういう例外なのかの共通理解が得られているからです。

例えば引数が不適切な場合のIllegalArgumentException(標準API)が代表例です。【Illegal:不正】な【Argument:引数】のExceptionという意味です。

この例外クラスを使ってPlayer4クラスを作成してみました。

マイナスの金額を引き出すテストをしてみます。

<実行結果>

出金額は正の整数のみです

残高(1000)を超えて引き出すテストをしてみます。

<実行結果>

残高不足です

このように意図した通りになりました。

オリジナルの例外クラスを作る 最後までお読みいただきありがとうございます。