メソッドのオーバーライド
親のメソッドが、子には合わない
親クラスに共通の fee() を書いておいたのに、速達だけは計算式が違う。かといって子に expressFee() という別名を用意すると、呼ぶ側が「これは速達だから別のメソッドを呼ぼう」と種類を意識することになります。
名前は親と同じままにして、中身だけ子で入れ替える。これが オーバーライド です。
シグネチャを完全に一致させ、@Override を付ける
子クラスで、親と 戻り値の型・メソッド名・引数 がまったく同じメソッドを書くだけです。
Java
class Shipping {
public int fee() {
return 500;
}
}
class ExpressShipping extends Shipping {
@Override
public int fee() {
return 500 + 300;
}
}new ExpressShipping().fee() は 800 を返します。親の fee() は隠れて、子の実装が選ばれます。
大事なのは「まったく同じ」の部分です。引数を 1 つ足したり型を変えたりすると、Java はそれを別のメソッドと見なします。親の fee() はそのまま生き残り、子には誰も呼ばないメソッドが 1 つ増えるだけ。呼んでも親の値が返ってきて、原因の分かりにくいバグになります。
@Override は「これは親のメソッドの差し替えのつもりだ」とコンパイラに伝える印です。付けても動きは変わりませんが、親に一致するメソッドが無ければコンパイルエラーになります。
fee を free とタイプミスしたとき、付けていなければ黙って通り、実行してから「なぜか元の値が返る」と悩むことになります。付けていれば method does not override or implement a method from a supertype で即座に止まります。差し替えるつもりのメソッドには必ず付けてください。
そのほかの決まりは次の 2 つです。
- アクセス修飾子は親と同じか、より広くだけ。親が
publicなのに子でprotectedにするとエラーになる staticやfinalが付いた親のメソッドは差し替えられない
親の型で受けても、子の実装が動く
差し替えが効くのは、変数の型ではなく 実際に入っているインスタンス を見て呼び先が決まるからです。
Java
Shipping s = new ExpressShipping();
int f = s.fee(); // 800。Shipping.fee ではない変数 s の型は Shipping ですが、中身は ExpressShipping なので、実行時に子の fee() が選ばれます。おかげで呼ぶ側は、相手が速達かどうかを知らないまま fee() と書けばよくなります。
やってみよう
circleArea(int radius) を完成させてください。
- 親クラス
Shapeにpublic int area()を用意する。中身は0を返すだけで構いません Shapeを継承したCircleを作り、int radiusフィールドとコンストラクタを持たせるCircle側でarea()を差し替え、(int)(radius * radius * 3.14)を返す。@Overrideを忘れずにcircleAreaの中ではShape shape = new Circle(radius);のように 親の型で受けて からarea()を呼ぶ
int へのキャストで小数点以下は切り捨てになります。半径が 1 なら 3.14 から 3、10 なら 314 です。手順 4 でわざわざ親の型を経由するのは、変数の型ではなく中身で呼び先が決まることを、自分の目で確かめるためです。
要件
- 親クラス
Shapeを用意し、public int area()を定義すること (中身は0でよい) - 子クラス
Circle extends Shapeを作り、@Overrideを付けてarea()を再定義し、(int)(radius * radius * 3.14)を返すこと circleAreaの中でShape shape = new Circle(radius);のように親の型で受けてshape.area()の値を返すこと (動的ディスパッチでCircle.areaが呼ばれる)
入出力例
circleArea(1) → 3
circleArea(10) → 314
circleArea(0) → 0
circleArea(5) → 78