オーバーライドとthrows
このレッスンでできるようになること。オーバーライドしたメソッドに書ける throws の範囲を判定し、コンパイルエラーになる形を見抜けるようになります。
まず予測してください
class Parent {
void run() throws java.io.IOException { }
}
class Child extends Parent {
@Override
void run() throws Exception { } // これは通るでしょうか
}通りません。「オーバーライドされたメソッドより広い例外は投げられません」というコンパイルエラーになります。Exception は IOException の親、つまりより広いからです。
規則は「広げるな、狭めるのは自由」
親のメソッドが throws している検査例外に対して、子は次のことができます。
- 同じ例外を書く
- その例外のサブクラスを書く(狭める)
- throws を丸ごと消す(何も投げない)
- 検査例外を1つも書かない代わりに、非検査例外を好きなだけ投げる
できないのは1つだけで、親が申告していない検査例外や、親より広い検査例外を足すことです。理由は使う側から考えると分かります。Parent 型の変数に Child を入れて run を呼ぶ人は、Parent の throws しか知りません。そこに知らない検査例外が飛んでくると、catch の書きようがないからです。
非検査例外は対象外
RuntimeException とその子孫は、親が何も書いていなくても子が自由に投げられます。throws に書いても書かなくても構いません。これを「広げているからエラーだ」と誤答させるのが定番の引っ掛けです。この規則が縛るのは検査例外だけと覚えてください。
どの throws が効くのか
呼び出し側でどの例外を catch しないといけないかは、変数の型(コンパイル時の型)で決まります。実際に入っているオブジェクトが throws を消した子クラスでも、変数が親の型なら親の throws に合わせて catch が要ります。ここもポリモーフィズムと絡めて出ます。
確かめる
演習では、検査例外を throws する親のメソッドを、検査例外を投げない子でオーバーライドします。throws を消しても正しくオーバーライドできること、そして呼び出し側は変数の型に従って catch を書くことを、実行して確認してください。
要件
- ChildStore で load(String key) をオーバーライドする
- ChildStore の load には throws を付けない
- キーが空文字なら IllegalArgumentException を投げる
- そうでなければ "child-" + key を返す
- Store と MissingKeyException と call には手を加えない
入出力例
call("base", "a") → "OK base-a"
call("child", "a") → "OK child-a"
call("base", "") → "MissingKeyException"
call("child", "") → "IllegalArgumentException"