LoginSignup
8
5

More than 5 years have passed since last update.

C#とJavaのオーバーライドの話

Posted at

ふとしたきっかけで、C#とJavaのオーバーライドの仕様、またC#(とC++)特有のnew修飾子の挙動について調べ直したので投稿します。
なお筆者は、C#からJava(Android)に移行し、Java側から見て執筆していることをご了承ください。

先に結論

  • C#
    1. 原則すべてのメソッドはオーバーライド不可能
    2. オーバーライドする場合、virtual修飾子とoverride修飾子とをペアで使う
    3. オーバーライドはクラスの「拡張」、new修飾子はクラスメンバの「隠ぺい」であり、対になる概念
  • Java
    1. 原則すべてのメソッドがオーバーライド可能(final宣言した場合を除く)
    2. オーバーライドする場合はOverrideアノテーション付与するだけ
    3. new修飾子は無い(new演算子のみ)

ひとつずつ見ていきます。

1. 原則オーバーライド可能?不可能?

このC#とJavaの違いは設計思想から来ているそうです。次の引用はC++についてですが、C#にも引き継がれている模様。

C++では、一般に仮想関数はコンパイル時にどのメンバー関数を呼び出すかを確定できないため、通常のメンバー関数呼び出しよりもパフォーマンスが悪いというデメリットがある。そのため、パフォーマンスを気にするC++プログラマには、継承する必要がないクラスのメンバー関数にvirtual修飾子をつけることを非常に嫌がる向きがある。
wiki:メソッド (計算機科学)

対して、

Javaはデフォルトで仮想的
wiki:メソッド (計算機科学)

なので、すべてのメソッドがデフォルトでオーバーライド可能になっています。
なお、「仮想的」とは、C#においてvirtual修飾子を付与したメンバを指し、オーバーライド可能であることを示します(詳細は次項)。

2. オーバーライドの書き方比較

C#の場合

C#では原則オーバーライドが不可能なため、オーバーライドする場合には次のような制約があります。

  1. オーバーライドされるメソッドはvirtual修飾子が必要
  2. 派生クラスでオーバーライドする場合、override修飾子が必要

記法は次のようになります。

class Parent {
    //被オーバーライドメソッド
    public virtual void Hoge() {...}
}

class Child : Parent { //Parentを継承
    public override void Hoge() {...} //オーバーライド
}

Javaの場合

一方、Javaは原則オーバーライド可能であるため、被オーバーライドメソッドには特に何もせず、派生クラス側でOverrideアノテーションを付与しオーバーライドします。

class Parent {
    //被オーバーライドメソッド
    public void hoge() {...}
}

class Child extends Parent { //Parentを継承
    //Overrideアノテーションを付けてオーバーライド
    @Override
    public void hoge() {...}
}

3. new修飾子って何

さて上記を踏まえた上で、C#のリファレンスを見てみます。

new と override には相反する意味があるため、同じメンバーにこの 2 つの修飾子を使用するとエラーになります。 new 修飾子は、同じ名前で新しいメンバーを作成し、元のメンバーを隠ぺいします。 override 修飾子は、継承されたメンバーの実装を拡張します。
new修飾子(C#リファレンス)

new修飾子はオーバーライドとは違い、基のメンバ1を隠蔽するとなっていますが、実際にコンソールプログラムを実行して確かめてみます。

class Program {
    static void Main(string[] args) {
        Parent parent = new Parent();
        Child child = new Child();
        Parent childInParent = new Child(); //コイツの動作が変わる

                                        //出力
        parent.Hoge();                  //Parent#Hoge()
        parent.VirtualMethod();         //Parent#VirtualMethod()

        child.Hoge();                   //Child#Hoge()
        child.VirtualMethod();          //Child#VirtualMethod()

        childInParent.Hoge();           //Parent#Hoge() ← Parentが出力(※1)
        childInParent.VirtualMethod();  //Child#VirtualMethod()

        Console.ReadLine();
    }
}

public class Parent {
    public void Hoge() {
        Console.WriteLine("Parent#Hoge()");
    }

    //virtualとoverride修飾子はペア(※3)
    public virtual void VirtualMethod() {
        Console.WriteLine("Parent#VirtualMethod()");
    }
}

public class Child : Parent {
    //new修飾子で隠ぺい(※2)
    public new void Hoge() {
        Console.WriteLine("Child#Hoge()");
    }

    //オーバーライド(※3)
    public override void VirtualMethod() {
        Console.WriteLine("Child#VirtualMethod()");
    }
}

C#のメソッドはすべて非仮想メソッドであるため、変数の型に応じてどのクラスのメソッドを呼ぶかが決まります。
上記のコードでは、childInParent変数はChild型のオブジェクトを代入しているのだからChild#Hoge()をコールしたいところ、変数がParent型であるため、Parent#Hoge()がコールされてしまいました(※1)。

一方で、Child型の変数childは、Hoge()をコールするとChild#Hoge()を出力します。なんか当たり前な感じがしますが、これはParent#Hoge()が同名のメソッドChild#Hoge()で隠ぺいされたことを示します(※2)。

また、オーバーライドする場合はvirtual修飾子を付与して仮想メソッド化し、それを派生クラスでoverride修飾子を付けて実装する形になります(※3)。

まとめ

結論を先に書いてしまったのでまとめも何もありませんが、Java沼からするとなんでこんなオーバーライドによく似た機能があるのか疑問に思うかもしれません。
Javaから見たC#の利点としては次のような感じだと思います。

  1. 仮想化によるパフォーマンス低下を防ぐ
  2. オーバーライド可能なメソッドを明示的に示せる

間違い等ありましたら修正いたします。また利点を教えていただけたらありがたいです。
読んでいただきありがとうございました。


  1. プロパティ、フィールドメンバ等も仮想化できますが、メソッドが最も使いそうなので省略 

8
5
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5