「オーバライド」はスーパークラスで定義されているメソッドの内容をサブクラスで上書きし、そのサブクラスのインスタンスを呼び出した時に上書きした方のメソッドを呼び出す機能です。
※ オーバーロードと言葉は似ていますが全く違う機能です。
オーバライドを使うとサブクラスのメソッドの動きをスーパークラスと根本的に変えることが可能になるため、オブジェクト指向プログラミングの必須機能として様々な場面で多用されています。
では順を追ってオーバライドについて説明します。
まず FRUIT スーパークラスを以下のソース 1 で定義します。
public class FRUIT {
public String name;
public void eat(){
System.out.println(name+"美味い");
}
}
この FRUIT スーパークラスを継承させて APPLE サブクラス(ソース 2 )と ORANGE サブクラス(ソース 3 )を作ります。
public class APPLE extends FRUIT{
public APPLE(){
name = "りんご";
}
}
public class ORANGE extends FRUIT{
public ORANGE(){
name = "みかん";
}
}
次に main メソッドとこの時点での実行結果をソース 4 に示しますが、ここで
apple と orange が見かけ上では FRUIT スーパークラスのインスタンスとして扱われている
ことに注意して下さい。
public class Main{
public static void main(String[] args) {
FRUIT apple = new APPLE(); // ※ 「APPLE apple = 〜」 ではない!
FRUIT orange = new ORANGE(); // ※ 「ORANGE orange = 〜」 ではない!
apple.eat();
orange.eat();
}
}
りんご美味しい みかん美味しい
この様に、オブジェクト指向プログラミングではサブクラスのインスタンスをスーパークラスの(見た目上の)インスタンスとして扱うことが出来ます。
この機能のことを「サブタイプ多相」と呼んでオーバライドの大前提となります。
さてここで orange.eat() を実行した際に「みかん不味い」に表示を変えたいと思います(apple.eat()はそのまま)。
もしオーバライドの事を知らないプログラマーなら FRUIT スーパークラスの eat() メソッドをおそらく次のソース 5 の様に書き換えるでしょう。
public class FRUIT {
public String name;
public void eat(){
if( name == "みかん" ) System.out.println(name+"不味い");
else System.out.println(name+"美味い");
}
}
しかしこのコードには
というように、サブクラスを追加する度に if 文が eat() に追加されて行くことになり、ソースの見た目や独立性、保守性が最悪なコードになるという問題があります。
よって、一般的には
スーパークラスのメソッド内ではサブクラス毎に条件分岐させる処理を書いてはいけません。
ではどうすれば良いかというと、スーパークラスのメソッドの内容をサブクラスでオーバライド、つまり上書きします。
例えば以下のソース 6 は FRUIT スーパークラスの eat メソッドを ORANGE サブクラスでオーバライドした例です。
public class ORANGE extends FRUIT {
public ORANGE(){
name = "みかん";
}
// FRUIT の eat メソッドをオーバライド
public void eat(){
System.out.println(name+"不味い");
}
}
すると ORANGE サブクラスのインスタンスを eat するとスーパークラスで定義した eat の代わりに上書された eat が呼び出されるようになります。
では具体的な実行結果を見てみましょう。
りんご美味しい みかん不味い
この様に、見かけ上は apple も orange も FRUIT クラスのインタンスであるにも関わらず、apple の方は FRUIT で定義された元々の eat メソッド、orange の方は上書きした eat メソッドが呼び出されていることが分かります。
ところでオーバライドを応用すると、次のソース 7 の様にメソッドの引数をスーパークラスにしたり、ソース 8 の様にスーパークラスのリストを作ってforEach を適用することも出来ます。
これらのテクニックは至るところで非常に良く使われていますので覚えておくと良いでしょう。
public class Main{
public static void Hoge( FRUIT fruit )
{
fruit.eat(); // ※ fruit が実際には APPLE なのか ORANGE なのかプログラマは気にする必要が無い!
}
public static void main(String[] args) {
APPLE apple = new APPLE(); // ※ apple は APPLE のインスタンス!
ORANGE orange = new ORANGE(); // ※ orange は ORANGE のインスタンス!
Hoge( apple );
Hoge( orange );
}
}
結果:
りんご美味しい
みかん美味しい
import java.util.ArrayList;
public class Main{
public static void main(String[] args) {
ArrayList<FRUIT> list = new ArrayList<FRUIT>();
list.add(new APPLE()); // ※ FRUIT のリストに APPLE のインスタンスを追加!
list.add(new ORANGE());// ※ FRUIT のリストに ORANGE のインスタンスを追加!
list.forEach( i -> i.eat() );
}
}
結果:
りんご美味しい
みかん美味しい