オーバーライド

オブジェクト指向プログラミングにおいてオーバーライド  とは、スーパークラスで定義されたメソッドをサブクラスで定義し直し、動作を上書きすることである。

例えば、あるクラスBaseにメソッドprintがあり、あるクラスDerivedがクラスBaseを継承したとする。そのとき、クラスDerivedはクラスBaseにあるメソッドprintをオーバーライドすることにより、再定義することができる。これはオブジェクト指向プログラミングにおけるポリモーフィズムを実現する際によく使われる。

通例、オーバーライドを可能とする条件として、メソッドの名前、引数の数と型の順序、そして戻り値の型が統一されている必要がある広義では「シグネチャが同じメソッド」とも言えるが、プログラミング言語ごとに「シグネチャ」の厳密な定義は異なる。}}。

メソッドのオーバーロードと名前は似ているが、まったく異なる概念である。

Rubyのようにオーバーロードの概念がなく、引数の型や数の条件がなくメソッド名が同一なだけでオーバーライドが成立するプログラミング言語もある。

1. オーバーライドの例
------------

1.1. Java
---------

Javaのインスタンスメソッドは仮想メソッドであり、派生クラスでオーバーライド可能である。インスタンスメソッドを非仮想にする手段はないが、finalキーワードを指定することでオーバーライドを禁止することはできる。

なお、Java SE 5から導入されたアノテーションname=@Overrideを用いることで、メソッドがオーバーライドされていることをコンパイラに知らせることができる。しかし、アノテーションの指定はオプションであり必須ではない。

// 基本クラス。 class Base { // コンストラクター Base { System.out.println"); }

// インスタンスメソッド void print { System.out.println"); }

// クラスメソッド static void staticPrint { System.out.println"); } }

// 派生クラス。 class Derived extends Base { // コンストラクター Derived { System.out.println"); }

// スーパークラスのインスタンスメソッド print をオーバーライドしている。@Override の指定は必須ではないが推奨される。 @Override void print { System.out.println"); }

// スーパークラスのクラスメソッド staticPrint をオーバーライドすることはできない。隠蔽することになる。 static void staticPrint { System.out.println"); } }

public class Main { public static void main { System.out.println; Base base = new Base; base.print;

System.out.println; Derived derived = new Derived; derived.print;

System.out.println; Base derivedInBaseVariable = new Derived; derivedInBaseVariable.print;

System.out.println; Base.staticPrint;

System.out.println; Derived.staticPrint; } }<WtXmlEndTag /> 実行結果: ■ Baseインスタンスメソッドの呼び出し: Base.Base Base.print ■ Derivedインスタンスメソッドの呼び出し: Base.Base Derived.Derived Derived.print ■ Base型変数を経由したDerivedインスタンスメソッドのポリモーフィック呼び出し: Base.Base Derived.Derived Derived.print ■ Baseクラスメソッドの呼び出し: Base.staticPrint ■ Derivedクラスメソッドの呼び出し: Derived.staticPrint

1.2. C#
-------

C#のオーバーライドの特徴として、以下が挙げられる。

仮想メソッドおよび抽象メソッドのオーバーライドの際にoverrideキーワードの指定が必要である。 ただし、インターフェイスのメソッドを実装する場合は、overrideの指定は不要  である。
メソッドは既定では非仮想であり、virtualを指定することでオーバーライド可能な仮想メソッドとなる。
プロパティ、インデクサ、イベントも、virtual修飾されている場合はメソッドと同様にオーバーライドの対象となる。
sealedキーワードを指定することでオーバーライドを禁止できる。コードの例を示す。

// 抽象基本クラス。 abstract class Base { // 既定では非仮想メソッド。 public void GoodMorning { Console.WriteLine; }

// virtualを指定することで仮想メソッドとなる。 public virtual void Hello { Console.WriteLine; }

public virtual void Goodbye { Console.WriteLine; }

// 抽象メソッド。 public abstract void GoodNight; }

// 派生クラス。 class Derived : Base { // 非仮想メソッドはオーバーライドできない。 // 同名のメソッドで隠蔽する場合、newを指定する。 // public new void GoodMorning { Console.WriteLine; }

// 仮想メソッドをオーバーライドする。 // overrideの指定が必須。 public override void Hello { Console.WriteLine; }

// 仮想メソッドをオーバーライドする。 // overrideと共にsealedを指定することで、このクラスを継承した先ではオーバーライドが禁止される。 public override sealed void Goodbye { Console.WriteLine; }

// 抽象メソッドをオーバーライドする。 // overrideの指定が必須。 public override void GoodNight { Console.WriteLine; } }

class DerivedDerived : Derived { // 非仮想メソッドはオーバーライド不可。 //public override void GoodMorning { Console.WriteLine; }

public override void Hello { Console.WriteLine; }

// sealedされたメソッドはオーバーライド不可。 //public override void Goodbye { Console.WriteLine; }

public override void GoodNight { Console.WriteLine; } }<WtXmlEndTag />

2. 言語固有の注意点
-----------

あるスーパークラスとそれを継承したサブクラスを定義する際、JavaやC++ではオーバーライドに関係した問題が起こりうるので注意が必要である。Javaのインスタンスメソッドは仮想メソッドであり、あとからスーパークラスにメソッドを追加したときに、そのメソッドと同じシグネチャのメソッドが既にサブクラスに存在すると、オーバーライドしたつもりがないのに関係のないメソッドをオーバーライドしてしまうという問題が起こる。逆にオーバーライドしたつもりでも、スーパークラスのメソッドシグネチャあるいはアクセシビリティの変更サブクラスから不可視なスーパークラスのメソッドと同じシグネチャを持つメソッドをサブクラスで定義することは可能であり、オーバーライドではなく別のメソッドとして認識される。}}や、サブクラスのメソッド定義時のtypoなどによって正しくオーバーライドできていなかった、といった問題も起こる。後者の問題を回避するためにJavaではname=@Overrideの指定が推奨されるが、後方互換性を保つため、アノテーションの指定は必須とはなっていない。C++においてもC++11でoverride修飾子が導入されたが、override修飾子の指定はオプションであり必須ではない。

// 基底クラス。 class Base { public Base { System.out.println"); this.init; }

// アクセス修飾子を変更して、派生クラスから見えるようにすると、 // Derived で意図せずオーバーライドしてしまうことになる。 // 結果として、コンストラクタの振る舞いが変わってしまう。 // 誤ってオーバーライドしてしまうことを防ぐには、final 指定する必要がある。 private void init { System.out.println"); } }

// 派生クラス。 class Derived extends Base { public Derived { System.out.println"); this.init; }

// もし基底クラスで同名の可視メソッドが定義されている場合、オーバーライドする。 public void init { System.out.println"); } }

public class Main { public static void main { Derived derived = new Derived; } }<WtXmlEndTag />

C#ではメソッドが既定で非仮想であり、またオーバーライドするには当初からoverride修飾子が必須なのでこの問題は起こらない。基底クラスを変更しても、破壊的な変更につながりにくくなっている。

// 基底クラス。 class Base { public Base { System.Console.WriteLine"); this.Init; }

// アクセス修飾子を変更して、派生クラスから見えるようにしても、 // Derived で意図せずオーバーライドしてしまうことにはならない。 // 仮に基底クラスで virtual 指定したとしても、派生クラスで override 指定が必須となるため、 // 意図せずオーバーライドしてしまうことにはならない。 private void Init { System.Console.WriteLine"); } }

// 派生クラス。 class Derived : Base { public Derived { System.Console.WriteLine"); this.Init; }

// もし基底クラスで同名の可視メソッドが定義されている場合、隠蔽する。 // new の指定は必須ではないが、new を指定せず隠蔽した場合はコンパイラが警告を出す。 public void Init { System.Console.WriteLine"); } }

public class Test { public static void Main { Derived derived = new Derived; } }<WtXmlEndTag />

KotlinやSwiftのような後発言語では、C#同様にオーバーライドにはoverrideの指定が必須となっている。

3. 脚注
-----

3.1. 注釈
-------

3.2. 出典
-------

4. 関連項目
-------

オブジェクト指向プログラミング
ポリモーフィズム
メソッド #仮想メソッド