オーバーライド
オブジェクト指向プログラミングにおいてオーバーライド (override) とは、スーパークラスで定義されたメソッドをサブクラスで定義し直し、動作を上書き(変更)することである。
例えば、あるクラスBase
にメソッドprint
があり、あるクラスDerived
がクラスBase
を継承したとする。そのとき、クラスDerived
はクラスBase
にあるメソッドprint
をオーバーライドすることにより、再定義することができる。これはオブジェクト指向プログラミングにおけるポリモーフィズム(多態性)を実現する際によく使われる。
通例、オーバーライドを可能とする条件として、メソッドの名前、引数の数と型の順序、そして戻り値の型が統一されている必要がある[注釈 1]。
メソッドのオーバーロード(多重定義、overload)と名前は似ているが、まったく異なる概念である。
Rubyのようにオーバーロードの概念がなく、引数の型や数の条件がなくメソッド名が同一なだけでオーバーライドが成立するプログラミング言語もある。
オーバーライドの例
編集Java
編集Javaのインスタンスメソッドは仮想メソッドであり、派生クラスでオーバーライド可能である。インスタンスメソッドを非仮想にする手段はないが、final
キーワードを指定することでオーバーライドを禁止することはできる。
なお、Java SE 5から導入されたアノテーション@Override
を用いることで、メソッドがオーバーライドされていることをコンパイラに知らせることができる(正しくオーバーライドされていない場合、コンパイルエラーとなる)。しかし、アノテーションの指定はオプションであり必須ではない。
// 基本クラス。
class Base {
// コンストラクター
Base() {
System.out.println("Base.Base()");
}
// インスタンスメソッド
void print() {
System.out.println("Base.print()");
}
// クラスメソッド
static void staticPrint() {
System.out.println("Base.staticPrint()");
}
}
// 派生クラス。
class Derived extends Base {
// コンストラクター
Derived() {
System.out.println("Derived.Derived()");
}
// スーパークラスのインスタンスメソッド print をオーバーライドしている。@Override の指定は必須ではないが推奨される。
@Override
void print() {
System.out.println("Derived.print()");
}
// スーパークラスのクラスメソッド staticPrint をオーバーライドすることはできない。隠蔽することになる。
static void staticPrint() {
System.out.println("Derived.staticPrint()");
}
}
public class Main {
public static void main(String[] args) {
System.out.println("■ Baseインスタンスメソッドの呼び出し:");
Base base = new Base();
base.print();
System.out.println("■ Derivedインスタンスメソッドの呼び出し:");
Derived derived = new Derived();
derived.print();
System.out.println("■ Base型変数を経由したDerivedインスタンスメソッドのポリモーフィック呼び出し:");
Base derivedInBaseVariable = new Derived();
derivedInBaseVariable.print();
System.out.println("■ Baseクラスメソッドの呼び出し:");
Base.staticPrint();
System.out.println("■ Derivedクラスメソッドの呼び出し:");
Derived.staticPrint();
}
}
実行結果:
■ 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()
C#
編集C#のオーバーライドの特徴として、以下が挙げられる。
- 仮想メソッドおよび抽象メソッドのオーバーライドの際に
override
キーワードの指定が必要である。
- ただし、インターフェイスのメソッドを実装する場合は、
override
の指定は不要 (不可) である。
- メソッドは既定では非仮想であり、
virtual
を指定することでオーバーライド可能な仮想メソッドとなる。 - プロパティ、インデクサ、イベントも、
virtual
修飾されている場合はメソッドと同様にオーバーライドの対象となる。 sealed
キーワードを指定することでオーバーライドを禁止できる。
コードの例を示す。
// 抽象基本クラス。
abstract class Base {
// 既定では非仮想メソッド。
public void GoodMorning() { Console.WriteLine("Good morning, Base!"); }
// virtualを指定することで仮想メソッドとなる。
public virtual void Hello() { Console.WriteLine("Hello, Base!"); }
public virtual void Goodbye() { Console.WriteLine("Goodbye, Base!"); }
// 抽象メソッド(実装を持たない)。
public abstract void GoodNight();
}
// 派生クラス。
class Derived : Base {
// 非仮想メソッドはオーバーライドできない。
// 同名のメソッドで隠蔽する場合、newを指定する。
// (ここでは指定しないが、さらにvirtualを指定することで仮想メソッドとなる)
public new void GoodMorning() { Console.WriteLine("Good morning, Derived!"); }
// 仮想メソッドをオーバーライドする。
// overrideの指定が必須。
public override void Hello() { Console.WriteLine("Hello, Derived!"); }
// 仮想メソッドをオーバーライドする。
// overrideと共にsealedを指定することで、このクラスを継承した先ではオーバーライドが禁止される。
public override sealed void Goodbye() { Console.WriteLine("Goodbye, Derived!"); }
// 抽象メソッドをオーバーライドする。
// overrideの指定が必須。
public override void GoodNight() { Console.WriteLine("Good night, Derived!"); }
}
class DerivedDerived : Derived {
// 非仮想メソッドはオーバーライド不可。
//public override void GoodMorning() { Console.WriteLine("Good morning, DerivedDerived!"); }
public override void Hello() { Console.WriteLine("Hello, DerivedDerived!"); }
// sealedされたメソッドはオーバーライド不可。
//public override void Goodbye() { Console.WriteLine("Goodbye, DerivedDerived!"); }
public override void GoodNight() { Console.WriteLine("Good night, DerivedDerived!"); }
}
言語固有の注意点
編集あるスーパークラスとそれを継承したサブクラスを定義する際、JavaやC++ではオーバーライドに関係した問題が起こりうるので注意が必要である(特にスーパークラスの実装者とサブクラスの実装者が異なる場合)。Javaのインスタンスメソッドは仮想メソッドであり、あとからスーパークラスにメソッドを追加したときに、そのメソッドと同じシグネチャのメソッドが既にサブクラスに存在すると、オーバーライドしたつもりがないのに関係のないメソッドをオーバーライドしてしまうという問題が起こる。逆にオーバーライドしたつもりでも、スーパークラスのメソッドシグネチャあるいはアクセシビリティの変更[注釈 2]や、サブクラスのメソッド定義時のtypoなどによって正しくオーバーライドできていなかった、といった問題も起こる。後者の問題を回避するためにJavaでは@Override
の指定が推奨されるが、後方互換性を保つため、アノテーションの指定は必須とはなっていない。C++においてもC++11でoverride
修飾子が導入されたが、override
修飾子の指定はオプションであり必須ではない。
// 基底クラス。
class Base {
public Base() {
System.out.println("Base.Base()");
this.init();
}
// アクセス修飾子を変更して、派生クラスから見えるようにすると、
// Derived で意図せずオーバーライドしてしまうことになる。
// 結果として、コンストラクタの振る舞いが変わってしまう。
// 誤ってオーバーライドしてしまうことを防ぐには、final 指定する必要がある。
private void init() {
System.out.println("Base.init()");
}
}
// 派生クラス。
class Derived extends Base {
public Derived() {
System.out.println("Derived.Derived()");
this.init();
}
// もし基底クラスで同名の可視メソッドが定義されている場合、オーバーライドする。
public void init() {
System.out.println("Derived.init()");
}
}
public class Main {
public static void main(String[] args) {
Derived derived = new Derived();
}
}
C#ではメソッドが既定で非仮想であり、またオーバーライドするには当初からoverride
修飾子が必須なのでこの問題は起こらない。基底クラスを変更しても、破壊的な変更につながりにくくなっている。
// 基底クラス。
class Base {
public Base() {
System.Console.WriteLine("Base.Base()");
this.Init();
}
// アクセス修飾子を変更して、派生クラスから見えるようにしても、
// Derived で意図せずオーバーライドしてしまうことにはならない。
// 仮に基底クラスで virtual 指定したとしても、派生クラスで override 指定が必須となるため、
// 意図せずオーバーライドしてしまうことにはならない。
private void Init() {
System.Console.WriteLine("Base.Init()");
}
}
// 派生クラス。
class Derived : Base {
public Derived() {
System.Console.WriteLine("Derived.Derived()");
this.Init();
}
// もし基底クラスで同名の可視メソッドが定義されている場合、隠蔽する。
// new の指定は必須ではないが、new を指定せず隠蔽した場合はコンパイラが警告を出す。
public void Init() {
System.Console.WriteLine("Derived.Init()");
}
}
public class Test {
public static void Main() {
Derived derived = new Derived();
}
}
Kotlin[1]やSwift[2]のような後発言語では、C#同様にオーバーライドにはoverride
の指定が必須となっている。