【Java】オーバーライドの壁を乗り越える

# はじめに

Javaを学ぶ上で、理解すべき考え方に継承やインターフェースがあります。
今回は、それらを使いこなすために必要な**オーバーライド**について説明したいと思います。

**※記事が長いので、簡潔に知りたい方は[まとめ](#まとめ)を参照してください。**

# ターゲット

- 継承やインターフェース実装の仕組みは知っている
- オーバーライドは何となく使えるが、ちゃんと理解したい
- **@Override**を知らない or 必要性がわからない

# 構成図

例として図形をテーマに、以下を使用します。

- Drawableインターフェース (描画のインターフェース)
- Mainクラス (図形を扱うクラス)
- Shapeクラス (図形を表すスーパークラス)
- Triangleクラス (Shapeクラスを継承した三角形を表すサブクラス)
- Squareクラス (Shapeクラスを継承した四角形を表すサブクラス)

```bash:構成図
├─interfaces
│      Drawable.java
│
├─main
│      Main.java
│
└─shapes
       Shape.java
       Square.java
       Triangle.java
```

# オーバーライドの基本と使用例

**オーバーライド**を直訳すると、**「~に優先する」「~を無効にする」**などの意味があります。
**Java**での**オーバーライド**は、継承したクラス(スーパークラス)や実装したインターフェースのメソッドを**再定義**することを指します。

### インターフェース

#### Drawableインターフェース

標準出力でOとXを用いて任意の出力をするDrawableインターフェースです。
DrawableインターフェースのdrawSubメソッドは、第三引数のcustomを任意とするためにオーバーロードしています。
今回はオーバーロードについては詳しく触れませんが、オーバーライドと関係が深いので、気になる方は調べてみてください。

```java:Drawable.java
package interfaces;

import java.util.Collections;
import java.util.List;

public interface Drawable {
    String POSITIVE = "O";
    String NEGATIVE = "X";

    String DEFAULT = "\u001B[0m";
    String RED    = "\u001b[00;31m";
    String GREEN  = "\u001b[00;32m";
    String YELLOW = "\u001b[00;33m";
    String PURPLE = "\u001b[00;34m";
    String PINK   = "\u001b[00;35m";
    String CYAN   = "\u001b[00;36m";
    String[] COLORS = new String[]{DEFAULT, RED, GREEN, YELLOW, PURPLE, PINK, CYAN};

    /**
     * 描く
     */
    abstract public void draw(int colorNo, boolean pattern);

    /**
     * 行を生成する
     */
    default public String drawSub(boolean pattern, int elemCount, List<Integer> custom) {
        String line = "";

        if (custom != null) {
            for (int i = 0; i < elemCount; i++) {
                line += custom.contains(i) ? POSITIVE : NEGATIVE;
            }
        } else if (pattern) {
            for (int i = 0; i < elemCount; i++) {
                line += i % 2 == 0 ? POSITIVE : NEGATIVE;
            }
        } else {
            line = String.join("", Collections.nCopies(elemCount, POSITIVE));
        }
        return line;
    };

    default public String drawSub(boolean pattern, int elemCount) { return drawSub(pattern, elemCount, null); };
}
```

### スーパークラス

#### Shapeクラス

図形のもととなるShapeクラスです。
Drawableインターフェースを実装した抽象クラスであり、具体的な図形を表すクラスはこれを継承します。

```java:Shape.java
package shapes;

import interfaces.Drawable;

public abstract class Shape implements Drawable{
    private int vertices;
    private String name;

    /**
    *  コンストラクタ
    */
    public Shape(int _vertices, String _name) {
        this.vertices = _vertices;
        this.name = _name;
    }

    /**
     * verticesのgetter
     */
    int getVertices() {
        return vertices;
    }

    /**
     * verticesのsetter
     */
    void setVertices(int _vertices) {
        this.vertices = _vertices;
    }

    /**
     * nameのgetter
     */
    String getName() {
        return name;
    }

    /**
     * nameのsetter
     */
    void setName(String _name) {
        this.name = _name;
    }

    /**
    *  面積を計算する
    */
    abstract public Number calcArea();

    /**
    *  図形の性質を表示する
    */
    public void displayProp() {
        System.out.println("名前：" + (this.name == null ? "未設定" : this.name));
        System.out.println("頂点の数：" + this.vertices);
    }
}
```
### サブクラス

#### Triangleクラス

三角形を表すTriangleクラスです。
図形の抽象クラスであるShapeクラスを継承しています。

```java:Triangle.java
package shapes;

public class Triangle extends Shape {

    private double height;
    private double base;

    public Triangle(double _height, double _base) {
        super(3, "三角形");
        this.height = _height;
        this.base = _base;
    }

    /**
     * verticesのgetter
     */
    protected int getVertices() {
        return super.getVertices();
    }

    /**
     * nameのgetter
     */
    protected String getName() {
        return super.getName();
    }

    /**
     * heightのgetter
     */
    public double getHeight() {
        return height;
    }

    /**
     * heightのsetter
     */
    public void setHeight(double _height) {
        this.height = _height;
    }

    /**
     * baseのgetter
     */
    public double getBase() {
        return base;
    }

    /**
     * baseのsetter
     */
    public void setBase(double _base) {
        this.base = _base;
    }

    /**
     * 三角形の面積を計算する
     */
    public Number calcArea() {
        return 0.5 * this.height * this.base;
    }

    /**
     *  三角形の性質を表示する
     */
    public void displayProp() {
        super.displayProp();
        System.out.println("高さ：" + this.height);
        System.out.println("底辺の長さ：" + this.base);
    }

    /**
     * 三角形を描く
     */
    public void draw(int colorNo, boolean pattern) {
        System.out.print(COLORS[colorNo]);
        for (int i = 0; i < height; i++) {
            System.out.println(drawSub(pattern, i+1));
        }
        System.out.print(DEFAULT);
    }
}
```

Shapeクラスを継承しているので、抽象メソッドであるcalcAreaメソッドの処理を定義しています。

```diff_java:calcAreaメソッドの差分イメージ
    /**
     * 三角形の面積を計算する
     */
    public Number calcArea() {
+       return 0.5 * this.height * this.base;
    }
```

displayPropメソッドについては処理をオーバーライドしています。

```diff_java:displayPropメソッドの差分イメージ
    /**
     *  三角形の性質を表示する
     */
    public void displayProp() {
-       System.out.println("名前：" + (this.name == null ? "未設定" : this.name));
-       System.out.println("頂点の数：" + this.vertices);
+       super.displayProp();
+       System.out.println("高さ：" + this.height);
+       System.out.println("底辺の長さ：" + this.base);
    }
```

また、ShapeクラスがDrawableインターフェースを実装しているので、drawメソッドを定義しています。

```diff_java:drawメソッドの差分イメージ
    /**
     * 三角形を描く
     */
    public void draw(int colorNo, boolean pattern) {
+       System.out.print(COLORS[colorNo]);
+       for (int i = 0; i < height; i++) {
+           System.out.println(drawSub(pattern, i));
+       }
+       System.out.print(DEFAULT);
    }
```

getter(getVerticesメソッド、getNameメソッド)は、[アクセス修飾子](#アクセス修飾子)で説明します。

このように、スーパークラスやインターフェースのメソッドをサブクラスで**オーバーライド**します。**オーバーライド**するときは、メソッド名と引数が同じである必要があります。

抽象クラスを継承した場合は、抽象クラスで定義されている抽象メソッドの処理を決める必要があるため、サブクラスで必ずオーバーライドを用います。インターフェースを実装する場合も同様です。
また、通常の(具象)メソッドについては、任意でオーバーライドできます。

**オーバーライド**した場合も、**スーパークラスのメソッド**を呼び出すことは**可能**です。
今回の例だと、TriangleクラスのdisplayPropメソッド内で```super.displayProp();```と書くことで、ShapeクラスのdisplayPropメソッドを呼び出しています。

#### Squareクラス

四角形を表すSquareクラスです。
Triangleクラスと同様にShapeクラスを継承しています。

```java:Square.java
package shapes;

public class Square extends Shape{

    private double length;
    private double width;

    public Square(double _length, double _width) {
        super(4, "四角形");
        this.length = _length;
        this.width = _width;
    }

    /**
     * verticesのgetter
     */
    @Override
    public int getVertices() {
        return super.getVertices();
    }

    /**
     * nameのgetter
     */
    @Override
    public String getName() {
        return super.getName();
    }

    /**
     * lengthのgetter
     */
    public double getLength() {
        return length;
    }

    /**
     * lengthのsetter
     */
    public void setLength(double length) {
        this.length = length;
    }

    /**
     * widthのgetter
     */
    public double getWidth() {
        return width;
    }

    /**
     * widthのsetter
     */
    public void setWidth(double width) {
        this.width = width;
    }

    /*
     * 四角形の面積を計算する
     */
    @Override
    public Double calcArea() {
        return this.length * this.width;
    }

    /*
     * 四角形の性質を表示する
     */
    @Override
    public void displayProp() {
        super.displayProp();
        System.out.println("縦の長さ：" + this.length);
        System.out.println("横の長さ：" + this.width);
    }

    /**
     * 四角形を描く
     */
    @Override
    public void draw(int colorNo, boolean pattern) {
        System.out.print(COLORS[colorNo]);
        for (int i = 0; i < length; i++) {
            System.out.println(drawSub(pattern, (int) this.width));
        }
        System.out.print(DEFAULT);
    }
}
```

Triangleクラスと同様にShapeクラスを継承していますが、オーバーライドの内容は少し違います。
四角形なので、面積の計算は```this.length * this.width```となっていたり、表示する性質が違ったり、図形を描く処理も違います。

このように、共通の意味を持つメソッドを抽象クラスやインターフェースで定義して、各サブクラスでそれぞれ違う処理に定義したいときの手段としてオーバーライドを使用します。

# オーバーライドはなぜあるのか

Javaでは、同じクラス内に**同名**かつ**同じ引数**を持つメソッドを定義することは、基本的にできないルールとなっています。
オーバーライドがないと仮定して、Triangleクラスに性質の表示メソッドを定義してみます。

```diff_java:Triangle.java(オーバーライドがない場合)
    /**
     *  三角形の性質を表示する
     */
-   public void displayProp() {
+   public void displayTriangleProp() {
        super.displayProp();
        System.out.println("高さ：" + this.height);
        System.out.println("底辺の長さ：" + this.base);
    }
}
```

このように、メソッド名を変えるのが一番無難ですね。
これでも問題ないように思いますが、この場合のデメリットはなんでしょう？

図形が三角形だけならさほど問題ではないでしょう。
でも、その他の図形と一緒に扱うときに手間がかかります。

例えば以下のようにそれぞれの図形の性質を表示する場合です。(Squareクラスにも同様の変更しているとします。)

```java:Main.java(オーバーライドなし)
package main;
import shapes.Square;
import shapes.Triangle;

public class Main {

    public static void main(String[] args) {

        Triangle triangle = new Triangle(30, 40);
        Square square = new Square(50, 60);

        triangle.displayTriangleProp();
        square.displaySquareProp();
}
```

三角形と四角形それぞれで処理を記述しています。
他の図形が増える度に、図形の性質を表示するメソッドの呼び出しを書かなければいけません。

では、オーバーライドがあるとどうでしょうか？

```java:Main.java(オーバーライドあり)
package main;
import shapes.Shape;
import shapes.Square;
import shapes.Triangle;

public class Main {

    public static void main(String[] args) {

        Triangle triangle = new Triangle(30, 40);
        Square square = new Square(50, 60);

        Shape[] shapes = {triangle, square};

        for (Shape shape : shapes) {
            shape.displayProp();
        }
    }
}
```

図形として配列にまとめて、for文で同じメソッド名で性質を表示することができます。
これなら他の図形に対しても、性質を表示するメソッドは同じように扱うことができます。

このように、同じ要素に対して色んな振る舞いを持たせることを**ポリモーフィズム(多態性・多相性)**といいます。
今回は、
**同じ要素 = メソッド名**
**振る舞い = メソッドを実行した時の結果**
です。

ポリモーフィズムはオブジェクト指向の中で重要な概念です。
ポリモーフィズムを実現する方法の一つとして、オーバーライドがあります。

# @Overrideアノテーションで安全に実装

オーバーライドにはいくつか条件があります。その条件を満たすことで初めてオーバーライドされます。
条件を満たさない場合は、以下のどちらかとなります。

1. コンパイルエラーとなる
1. 別のメソッドとして定義される

例えば、**引数を間違えた場合**などは**オーバーロード**となり、気づかないまま**別のメソッドとして定義**されてしまいます。

そのようなミスを防止するため、オーバーライドしたメソッドをコンパイラが分かるように、**アノテーション**が用意されています。それが**```@Override```**です。

Squareクラスを例にして、アノテーションの使用方法を見てみましょう。

```java:Square.java(オーバーライドアノテーション使用例1)
    /*
     * 四角形の面積を計算する
     */
    @Override
    public Double calcArea() {
        return this.length * this.width;
    }

    /*
     * 四角形の性質を表示する
     */
    @Override
    public void displayProp() {
        super.displayProp();
        System.out.println("縦の長さ：" + this.length);
        System.out.println("横の長さ：" + this.width);
    }
```

オーバーライドアノテーションは、オーバーライドしたメソッドの上に```@Override```と記述します。
使用することで、オーバーライドの条件を満たしていない場合にコンパイルエラーとなるため、安全に実装することができます。

例として、displayPropメソッドの引数を変更してみます。

```java:Square.java(オーバーライドアノテーション使用例2-1)
    /*
     * 四角形の性質を表示する
     */
    @Override
    public void displayProp(int n) {
        super.displayProp();
        System.out.println("縦の長さ：" + this.length);
        System.out.println("横の長さ：" + this.width);
    }
```

引数が不一致だと通常はオーバーロードとなり、別のメソッドとして定義されますが、オーバーライドアノテーションがあるとオーバーロードされずコンパイルエラーが起こります。

```bash:コンパイル結果(オーバーライドアノテーション使用例2-2)
Square.java:54: エラー: メソッドはスーパータイプのメソッドをオーバーライドまたは実装しません
    @Override
    ^
エラー1個

```

このようなミスを防ぐことに加えて、可読性の向上も見込めます。

記述しなくても問題は起こりませんが、得られる恩恵は大きいと思います。
そのため、オーバーライドしたメソッドを```@Override```で明示することをお勧めします。

# オーバーライドの条件

いくつかは既に触れていますが、オーバーライドをするときには守らなければいけない条件があります。
これを理解することで、抽象クラスやインターフェースの設計にも役立つと思いますので、確認していきましょう。

## メソッド名

メソッド名は、オーバーライドしたいメソッドと同じ名前で定義する必要があります。

例として、ShapeクラスのcalcAreaメソッドのオーバーライドをサブクラスで行う場合は、メソッド名が「calcArea」となっていることが分かります。

```java:Shape.java-calcAreaメソッド
    /**
    *  面積を計算する
    */
    abstract public Number calcArea();
```

```java:Triangle.java-calcAreaメソッド
    /**
     * 三角形の面積を計算する
     */
    @Override
    public Number calcArea() {
        return 0.5 * this.height * this.base;
    }
```

```java:Square.java-calcAreaメソッド
    /*
     * 四角形の面積を計算する
     */
    @Override
    public Double calcArea() {
        return this.length * this.width;
    }
```

## 引数

メソッドの引数は、型と順序の両方を一致させる必要があります。

例として、Drawableインターフェースのdrawメソッドに対するオーバーライドを見てみましょう。

```java:Drawable.java-drawメソッド
    /**
     * 描く
     */
    abstract public void draw(int colorNo, boolean pattern);
```

```java:Triangle.java-drawメソッド
    /**
     * 三角形を描く
     */
    public void draw(int colorNo, boolean pattern) {
// ...省略
```

```java:Square.java-drawメソッド
    /**
     * 四角形を描く
     */
    @Override
    public void draw(int colorNo, boolean pattern) {
// ...省略
```

drawメソッドをオーバーライドするので、第一引数がint型、第二引数がboolean型の必要があります。
例え引数の型が同じでも、**順序**を変えた場合はオーバーロードとなるので気を付けてください。(@Overrideがあればコンパイルエラーです。)

型と違って、引数の命名は変更しても問題ありません。

## アクセス修飾子

オーバーライドでは、アクセス修飾子を変更することができます。
しかし、条件としてオーバーライドするメソッドより可視性を下げることはできません。

アクセス修飾子の可視性は以下です。

| アクセス修飾子 | 同一クラス | 同一パッケージ | サブクラス | 全体 |
|:-:|:-:|:-:|:-:|:-:|
| public  |  O | O  |  O |  O |
| protected  | O  |  O |  O | X  |
| 指定なし(packege private)  |  O |  O | X  |  X |
|  private | O  |  X |  X |  X |

参考：[【解決Java】アクセス修飾子(protected、privateなど)](https://www.sejuku.net/blog/22679)

つまり、
**private < 指定なし < protected < public**
の順で可視性が上がります。

もとのメソッドが**protected**の場合は、**protected**と**public**を指定できますが、**指定なし**や**private**はできません。

例として、getterのオーバーライドを見てみましょう。

```java:Shape.java
// ...省略
    /**
     * verticesのgetter
     */
    int getVertices() {
        return vertices;
    }
// ...省略
    /**
     * nameのgetter
     */
    String getName() {
        return name;
    }
// ...省略
```

```java:Triangle.java
// ...省略
    /**
     * verticesのgetter
     */
    protected int getVertices() {
        return super.getVertices();
    }

    /**
     * nameのgetter
     */
    protected String getName() {
        return super.getName();
    }
// ...省略
```

```java:Square.java
// ...省略
    /**
     * verticesのgetter
     */
    @Override
    public int getVertices() {
        return super.getVertices();
    }

    /**
     * nameのgetter
     */
    @Override
    public String getName() {
        return super.getName();
    }
// ...省略
```

Shapeクラスでは、**指定なし**です。
Triangleクラスでは**protected**に、Squareクラスでは**public**にオーバーライドされています。
どちらの場合も**指定なし**より可視性が上ですので、オーバーライドできます。

## 戻り値

戻り値については、型をオーバーライドすることができます。
条件として、オーバーライドするメソッドで定義されている型のサブクラスである必要があります。

例としてcalcAreaメソッドを見てみます。

```java:Shape.java-calcAreaメソッド
    /**
    *  面積を計算する
    */
    abstract public Number calcArea();
```

```java:Triangle.java-calcAreaメソッド
    /**
     * 三角形の面積を計算する
     */
    public Number calcArea() {
        return 0.5 * this.height * this.base;
    }
```

```java:Square.java-calcAreaメソッド
    /*
     * 四角形の面積を計算する
     */
    @Override
    public Double calcArea() {
        return this.length * this.width;
    }
```

Shapeクラスは戻り値の型が**Number**で定義されており、Triangleクラスも同じく**Number**です。
しかし、Squareクラスは**Double**でオーバーライドされています。
これが実現できるのは、**DoubleがNumberのサブクラス**だからです。

このように、オーバーライドするメソッドで定義された戻り値に対して、そのサブクラスの型でオーバーライドした戻り値を**共変戻り値**といいます。

今回の例だと、スーパークラスのcalcAreaメソッドの戻り値の型はNumberなので、Double以外にもByte, Float, Integer, Long, Shortなどにオーバーライドできます。

# まとめ

**オーバーライド**は、継承したクラスや実装したインターフェースのメソッドを**再定義**することをいいます。

条件については、最初の定義を守らなければいけないということです。
**メソッド名**は**変更不可**です。

**引数の順序や型**を変えるとオーバーロードになってしまいますので、同じく**変更不可**です。

**アクセス修飾子**は、**可視性が同一か上**の必要があります。最初の定義で許可した部分を、後から制限してはいけないと考えると理解しやすいです。

**戻り値**についても、アクセス修飾子と同様に考えるとわかりやすいです。**同一の型**もしくは**共変戻り値**として**サブクラスの型**でオーバーライドすれば、最初の定義を守ることができます。
Number型をDouble型でオーバーライドしても、Double型はNumber型であるとも考えられるため、最初の定義を守っていると考えられます。

# 参考

[共変戻り値型（covariant return type）](http://www.ne.jp/asahi/hishidama/home/tech/java/covariant.html)
[君は合成メソッドを知っているか！(Java)](https://www.sakatakoichi.com/entry/20080805/1217925680)
[【解決Java】アクセス修飾子(protected、privateなど)](https://www.sejuku.net/blog/22679)