オーバーライドのコードトレース

オーバーライドは継承したインスタンスメソッドを子クラスで再定義する仕組みです。同じ呼出しでも実体の型に応じた処理が選ばれます。引数が異なるオーバーロード、staticメソッドの隠蔽、フィールドの隠蔽とは区別します。

この単元で学ぶこと

  • オーバーライドの成立条件を判定する
  • 宣言型と実体型を分けて呼出し結果を求める
  • オーバーロード・static・finalの違いを判断する

本文解説

オーバーロード選択と上書き先 親にf(Object)があり子にf(String)を追加した場合、Parent型の変数からf("x")を呼んでも子だけのString版は候補に入りません。コンパイル時に宣言型からシグネチャを選び、そのシグネチャの上書きがあれば実体で実行先を決めます。

上書きと追加を同時に持つ 子がf(Object)を上書きしf(String)も追加した場合、Parent p=new Child();p.f("x")は子のObject版です。Child型変数から同じ文字列を渡すと、より具体的なString版を選べます。実体が同じでも呼出し側の宣言型で候補が変わります。

動的呼出しの連鎖 親のrun(){return value()+1;}を子が継承しvalueだけ上書きすると、runの中のvalueも子実装を呼びます。runを宣言したクラスだけで内部の全呼出しが固定されるわけではありません。

戻り値型による利用可能操作 親のObject value()を子でString value()へ変えても、Parent型変数から呼んだ式の静的型はObjectです。p.value().length()は不正ですが、Child型変数c.value().length()はStringの操作として使えます。実行時の値とコンパイル時の式の型を区別します。

親実体と子実体を同じ配列で扱う Parent[]へParentとChildを入れ、それぞれvalue()を呼ぶと実体ごとの実装が動きます。親配列というだけで全要素の処理が親実装へ固定されません。

例外の宣言制約 親がチェック例外を宣言しないメソッドに、子がthrows Exceptionを追加することはできません。呼出し側の契約を勝手に増やすためです。非検査例外のIllegalArgumentException等は別の規則であり、宣言せず送出できます。

privateとOverride注釈 親privateの同名メソッドへ子が@Overrideを付けると、上書き対象がないためエラーです。注釈なしで同名を宣言できることと、オーバーライドできることは別です。

継承の途中で上書きする A→B→Cという関係でBが上書きしCが変更しなければ、C実体はB実装を使います。Cがさらに上書きすればC実装です。名前と引数が同じ系列を追って、実体から近い適切な実装を選びます。

図解

宣言型で検査し実体で実行
引数が違うオーバーロードの選択は別段階で行われる。

重要ポイント

  • 同じ名前と引数型を持つ継承インスタンスメソッドを再定義する。
  • 親型参照でも実体のオーバーライドが選ばれる。
  • 公開範囲を狭くできず、戻り値型にも互換性が必要。
  • finalメソッドはオーバーライド不可、staticは動的オーバーライドではない。

間違えやすい点

  • 引数名の変更を別のメソッドだと考えない。
  • 引数型を変えた宣言をオーバーライドと思わない。
  • @Overrideが動的な実行先を作る魔法の指定だと考えない。

用語解説

オーバーライド
子が継承インスタンスメソッドを同じシグネチャで再定義すること。
オーバーロード
名前が同じで引数型や数が違う複数宣言を用意すること。
動的メソッド呼出し
実体の型に基づきオーバーライド先を実行する仕組み。

関連教材

参考資料

教材データに登録された参考リンクです。リンク先の内容や更新日は提供元でご確認ください。

確認問題の出題・採点や学習履歴の保存にはログインが必要です。