例外を宣言したメソッドをサブクラスのメソッドでオーバーライドする場合、いくつかルールがあるので注意が必要です。ルールについて無理やり覚えるというのも良いですが、抽象に対してプログラミングを行う考え方であるポリモーフィズムという視点でルールの挙動を捉えれば、より覚えやすくなるでしょう。ここではルールの紹介をするとともに、ポリモーフィズムに基づく考え方について説明していきたいと思います。まず、例外のオーバーライドのルールは次のとおりです。スーパークラス側のメソッドが例外を投げるか投げないかで場合分けしています。
1.スーパークラスに定義しているメソッドが例外を投げない場合
①スーパークラスのメソッドが例外を投げない場合、サブクラスでオーバーライドするメソッド側で検査例外を投げることはできません。②なお、非検査例外を投げるよう宣言することはできます。
2.スーパークラスに定義しているメソッドが例外を投げる場合
スーパークラスのメソッドが例外を投げる場合、サブクラスでオーバーライドするメソッド側にはオーバーライドされる側と同じ例外か、オーバーライドされるメソッドで宣言した例外のサブクラス例外を宣言できます。また、例外なしとすることも可能です。ただし、オーバーライドされるメソッドで宣言した例外クラスのスーパークラスにあたる例外を宣言することはできません。
1.①ソース例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import java.io.*; class Super{ void msg(){ System.out.println("super"); } } class ExceptionSub extends Super{ void msg() throws IOException{ System.out.println("exceptionSub"); } public static void main(String args[ ]){ Super s = new ExceptionSub(); s.msg(); } } |
実行結果
コンパイルエラー |
スーパークラス側のメソッドで例外を宣言していないのに、サブクラスに定義したメソッドでIOExceptionという検査例外を宣言していますので、これはルールの1.①に引っ掛かりコンパイルエラーとなります。
1.②ソース例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import java.io.*; class Super{ void msg(){ System.out.println("super"); } } class ExceptionSub extends Super{ void msg() throws ArithmeticException{ System.out.println("exceptionSub"); } public static void main(String args[ ]){ Super s = new ExceptionSub(); s.msg(); } } |
実行結果
1 |
exceptionSub |
スーパークラスのメソッドが例外を投げないと定義している以上、サブクラス側のメソッドで検査例外を投げる(例外処理が必要になる)といったような矛盾する宣言はできません。なお、非検査例外はそもそも例外処理を書いても書かなくても良いという性質であるため、スーパークラスのメソッドの例外宣言と矛盾しません。よって、サブクラス側のメソッドに追加することができます。
2.ソース例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import java.io.*; class Super{ void msg() throws IOException { System.out.println("super"); } } class ExceptionSub extends Super{ void msg(){ System.out.println("exceptionSub"); } public static void main(String args[ ]){ Super s = new ExceptionSub(); try { s.msg(); } catch(IOException e){ } } } |
実行結果
1 |
exceptionSub |
2.のソース例ではまずs.msg()をtryブロックで囲っていることに着目すると良いでしょう。例外処理をするかしないかは、あくまで参照変数の型のメソッドで決まります。ポリモーフィズムの核心は、ある機能を実行する際に操作のやり方とその結果さえわかっていれば、実装部分がそれをどう実現しているかについて、使う側は気にする必要がないという考えにあります。例外処理に関しても同様で、機能を実装しているインスタンスのメソッドが例外を投げないと宣言していても、参照変数の型でメソッドが例外を投げると宣言している以上、例外処理をしないとコンパイルエラーになります。
この考え方が理解できれば、スーパークラス側のメソッドで例外を投げるよう宣言している際にサブクラス側のメソッドで投げられる例外は「スーパークラスのメソッドと同じ例外」・「サブクラスの例外」・「何も投げない」のどれかでないといけないというのは理解できると思います。例えば参照変数の型のメソッドがIOExceptionを投げると宣言してあるなら、その参照変数を使う側としては、ざっくり見てIOExceptionに含まれるものであれば許容できる(例外処理を前もってきちんと書ける)わけです。これがもしIOExceptionよりも広いExceptionを実は投げるんです・・・なんてことになれば、そのメソッドを安心して使えません。
そのような視点で考えてみるとルール1も同様の考え方ができそうです。参照変数に載っているメソッドは例外を投げないと宣言しているのに、メソッドを使ったら実は実装部分が例外を投げてくる、なんてことになれば安心してそのメソッドを使えません。
こうした矛盾が生じないように設定されているのがオーバーライドにおける例外処理のルールなのです。オブジェクト指向における部品化とそのためのポリモーフィズムという考え方でルールを確認すると、一見複雑なものでもきちんと芯が通ったものであるということが見えてくるかと思います。