Javaの資格試験(Silver)の出題範囲の中でも、例外に関する問題が難しく、KENスクールの受講生からもよく質問されます。今回は、その中でも特徴的な問題について解説します。
まずは下記サンプルを確認してください。
[SampleSuper.java]
1 2 3 4 5 6 |
class SampleSuper{ void method() throws Exception{ System.out.println("SampleSuper"); throw new Exception(); } } |
[SampleSub.java]
1 2 3 4 5 |
class SampleSub extends SampleSuper{ void method(){ System.out.println("SampleSub"); } } |
SampleSuperクラスはthrowsを含むメソッドを持つクラスで、SampleSubクラスは先のクラスを継承し、オーバーライドをしているメソッドを持つクラスです。
まずはこの2つのクラスを記述した状態でコンパイルをしたらどうなるでしょうか?
SampleSubクラスのmethod()メソッドにはthrowsがありませんが、オーバーライドとしては問題ないので、コンパイルは成功します。(今回の本題はここではないので、この部分についての説明は割愛します。)
ここからは前述の2つのクラスを使用したサンプルです。
もし、試験問題として出題されるなら、「それぞれのクラスをコンパイルした場合、どのようになるでしょうか?」や「コンパイルの成功に必要な対処をした場合、それぞれの実行結果はどのようになりますか?」のようになります。
試験は選択式ですが、今回は練習ですので選択肢に頼らず答えを考えてみてください。
まずは、1つ目のサンプルです。
[SampleExc1.java]
1 2 3 4 5 6 7 |
class SampleExc1{ public static void main(String[] args){ // 参照:親 インスタンス:親 SampleSuper s1 = new SampleSuper(); s1.method(); } } |
上記のサンプルをコンパイルした場合は、コンパイルエラーになります。SampleSuperクラスのオブジェクトの参照を持つs1から呼び出されるメソッドはthrowsを含むメソッドなので、例外に対処する必要があります。このサンプルにtry-catch文を追記し、例外に対処した後、コンパイルして実行した場合は、「SampleSuper」と表示されます。
次に、2つ目のサンプルです。
[SampleExc2.java]
1 2 3 4 5 6 7 |
class SampleExc2{ public static void main(String[] args){ // 参照:子 インスタンス:子 SampleSub s2 = new SampleSub(); s2.method(); } } |
上記のサンプルをコンパイルした場合は、コンパイルが成功します。SampleSubクラスのオブジェクトの参照を持つs2から呼び出されるメソッドはthrowsを含まないメソッドなので、例外に対処する必要はありません。そして、実行した場合は、「SampleSub」と表示されます。
最後に、3つ目のサンプルです。
[SampleExc3.java]
1 2 3 4 5 6 7 |
class SampleExc3{ public static void main(String[] args){ // 参照:親 インスタンス:子 SampleSuper s3 = new SampleSub(); s3.method(); } } |
上記のサンプルをコンパイルした場合は、コンパイルエラーになります。この部分についての解説の前に、実行した場合を考えます。コンパイルエラーに対処し、実行した場合は、「SampleSub」と表示されます。
このような難解である問題を解くには、例外とオーバーライドとコンパイルについて正しく理解をしている必要があります。
まずは実行結果から見てみます。実行結果が「SampleSub」となるのは、4行目で「new SampleSub()」として生成されている、SampleSubクラスのオブジェクトのメソッドを呼び出しているからです。例外に関する部分を除外して考えれば、特に問題はないと思います。(この部分が曖昧な方は「参照型のキャスト」や「オーバーライド」について再確認しましょう。)
そして、一番の問題である、コンパイルエラーとなる原因について見てみます。この部分でコンパイルエラーにならないと思った方は、「オーバーライドされているからSampleSubの方のメソッドが実行される」そして「SampleSubのメソッドの方にはthrowsが含まれないから、例外への対策なしで呼び出してもコンパイルエラーにならない」と考えたのだと思います。この2つの考え方のそれぞれは間違っていませんが、この2つを同列に考え、直接繋げるとおかしなことになります。
前者は実行時のことで、後者はコンパイル時のことです。
試験問題の選択肢に、「実行時に例外が発生する」「コンパイル時にエラーになる」というのをよく見ると思います。今回のサンプルもそうですが、【コンパイル】と【実行】のそれぞれが何のためのものなのか、何をやっているのか、を整理しておく必要があります。簡単にまとめると、【コンパイル】は文法のチェックをすることで、【実行】は実際に値やオブジェクトを扱って動作させることです。
本題に戻って考えると、コンパイルエラーとなるのは、文法としてダメな部分があるからです。実行時はSampleSubのメソッドが呼び出されますが、それは実行時の話で、コンパイルは記述上のチェックしかしません。5行目の「s3.method();」が問題の箇所ですが、この変数s3は何型の変数でしょうか?4行目に変数s3が宣言されています。変数s3は(SampleSubのオブジェクトを参照していますが)SampleSuper型の変数です。ですので、5行目のコードは、SampleSuperのメソッドを呼び出していることになります。
このようにコードをチェックするのがコンパイルで、実際にオブジェクトを生成して、メソッドを呼び出すのが実行です。
初めは考え方が難しいですが、多くの問題に触れることで慣れていきましょう!