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

ふとしたきっかけで、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:メソッド (計算機科学)](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%A6))

対して、
> Javaはデフォルトで仮想的
[wiki:メソッド (計算機科学)](https://ja.wikipedia.org/wiki/%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89_(%E8%A8%88%E7%AE%97%E6%A9%9F%E7%A7%91%E5%AD%A6))

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

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

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

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

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

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

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

```java
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#リファレンス）](https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/keywords/new-modifier)

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

```csharp
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]: プロパティ、フィールドメンバ等も仮想化できますが、メソッドが最も使いそうなので省略