【Java】ポリモーフィズム（Polymorphism）

#ポリモーフィズムとは
* 同じ名前のメソッドで異なる挙動を実現
* Triangle,RectangleクラスでShapeクラスを継承し、同名のgetAreaメソッドを定義
* 同じShape型の変数でもオブジェクトの型が違うので、オブジェクト型に応じて基底/派生クラスのメソッドが呼ばれる
* **→異なる機能を同じ名前で呼ぶことができる！**
* メリット：
    * 保守性に優れる
        * 機能の差し替えはインスタンスの差し替えのみ
    * 理解しやすい

```java:Shape.java
public class Shape {
  protected double width;
  protected double height;

  public Shape(double width, double height) {
    this.width = width;
    this.height = height;
  }
//図形の面積を取得（派生クラスでオーバーライドする）
  public  double getArea() {
    return 0d;
  }
  //public abstract double getArea();
}
```

```java:Triangle.java
public class Triangle extends Shape {
  public Triangle(double width, double height) {
    super(width, height);
  }
//三角形の面積取得
  @Override
  public double getArea() {
    return this.width * this.height / 2;
  }
}
```

```java:Rectangle.java
public class Rectangle extends Shape {
  public Rectangle(double width, double height) {
    super(width, height);
  }
//四角形の面積を取得
  @Override
  public double getArea() {
    return this.width * this.height;
  }
}
```

```java
public class PolymorphismBasic {
  public static void main(String[] args) {
    //Triangle型のオブジェクトを代入（アップキャスト） 
    Shape tri = new Triangle(10, 50);
    //Rectangle型のオブジェクトを代入（アップキャスト） 
    Shape rec = new Rectangle(10, 50);
    System.out.println(tri.getArea()); //250.0
    System.out.println(rec.getArea()); //500.0
  }
}
```

##抽象メソッド
* 上の例では基底クラスShapeがgetAreaメソッドをオーバーライド期待しているが強制ではない
* **抽象メソッド**：それ自体は中身（機能）を持たない**空メソッド**
* **抽象クラス**：抽象メソッドを含んだクラス
    * 抽象クラスを継承したクラスは**全抽象メソッドをオーバーライドする義務を持つ**
* 抽象メソッドによって**特定のメソッドが派生クラスでオーバーライドされることを保証できる**

###使い方
* **抽象メソッド定義には`abstract`修飾子をつける**
    * 基底クラスでブロック中身を持つことはできず、`;`で宣言終了するのみ
* **抽象メソッドを含んだクラスにはclassブロックに`abstract`修飾子をつける**

```java:Shape.java
public abstract class Shape {
  protected double width;
  protected double height;

  public Shape(double width, double height) {
    this.width = width;
    this.height = height;
  }
 //基底クラスで中身を持つことはできない！
  public abstract double getArea();
}
```

##インターフェース
* Javaでは多重継承を認めないので、一度に継承できるクラスは常に１つのみ
    * 不要なメソッドもとりあえずオーバーライドしないといけない
* →**インターフェース**を使う
    * 配下メソッドが全部抽象メソッドであるクラス
    * 多重継承が可能
    * それぞれのメソッドは意味的な塊で異なるインターフェースに振り分ける
    * 派生クラス側で必要なメソッドのみ選択できる
    
###インターフェースの定義
* `interface`命令
    * **public**：全クラスからアクセス可能
    * **abstract**：抽象クラス
    * **strictfp**：浮動小数点を環境依存しない方法で演算
* 配下に抽象メソッドを含むのは明らかなのでabstruatはつけなくても同じ（一般的に省略）
* インターフェース名はPascal記法

```java
public interface Shape {
  double getArea();
}
```
###定義可能メンバー
* 抽象メソッド
* defaultメソッド
* クラスメソッド
* 定数フィールド
* 入れ子staticクラス/インターフェース

###インターフェース実装
* **定義済みインターフェースを継承してクラスを定義すること**
* インターフェース実装したクラスを**実装クラス**という
* `implements インターフェース名`
* 複数インターフェース実装する時は`implements Shape, Hoge, Bar`
* 継承と組み合わせて`extends MyParent implements Shape`

```java
//Shapeインターフェースの実装クラス
public class Rectangle implements Shape {
  private double width;
  private double height;

  public Rectangle(double width, double height) {
    this.width = width;
    this.height = height;
  }

  @Override
  public double getArea() {
    return this.width * this.height;
  }
}
```

###インターフェースメンバー
* 抽象メソッド、定数フィールド、defaultメソッド、staticメソッド（Java8以降）

####定数フィールド
* **インターフェースはインスタンス化できない**のでインターフェースフィールドは定義不可
* フィールドは無条件に`public static final`
* 冗長なので以下のように書く

```java
interface MyApp{
    String TITLE = "不死鳥の騎士団";
    double NUMBER = 5;
}
```

####staticフィールド（Java8以降）
* 既存のインターフェースに関連して何らかの機能をまとめておきたい時

####defaultメソッド（Java8以降）
* 実装クラス側で明示的に実装されない場合既定で採用される実装

```java
public interface Loggable {
  default void log(String msg) {
    System.out.println("Log: " + msg);
  }
}
```
```java
//logメソッドをオーバーライドしない
public class LoggableImpl implements Loggable {
}
```
```java:InterfaceDefault.java
public class InterfaceDefault {

  public static void main(String[] args) {
    var l = new LoggableImpl();
    //Loggableクラスのdefaultメソッドが呼ばれる
    l.log("Harry Potter"); //Log:Harry Potter
  }
}
```
* superで明示的に実装クラスからdefaultメソッドを呼ぶこともできる

```java
//logメソッドをオーバーライドしない
public class LoggableImpl implements Loggable {
  @Override
  public void log(String msg) {
    Loggable.super.log(msg);
    System.out.println("LogImpl: " + msg);
  }
}
```
```java:InterfaceDefault.java
public class InterfaceDefault {

  public static void main(String[] args) {
    var l = new LoggableImpl();
    l.log("Harry Potter"); 
    //Log:Harry Potter
    //LogImpl:Harry Potter
  }
}
```

###多重継承の注意
####抽象メソッドが重複
* **メソッド名と引数型が同じで戻り値のみ異なるときコンパイルエラー**
* 実装側の戻り値は最下位の派生型である必要
　

```java
public interface IHoge {
  void foo();
  //CharSequence foo(); //OK
}
```
```java
public interface IHoge2 {
  String foo();
}
```
```java
public class HogeImpl implements IHoge, IHoge2 {
    @Override
    public String foo() { } //戻り値の型がIHoge.foo()と互換性なし
}
```

####定数フィールドが重複
* **データ型が異なる場合も、型/値が等しい場合も、参照時エラー**

```java
public interface Hoge {
  int DATA = 0;
}
```
```java
public interface Hoge2 {
  String DATA = "This is an apple.";
}
```
```java
public class HogeImpl implements Hoge, Hoge2 {
  public void foo() {
    System.out.println(DATA); //エラー
  }
}
```
####defaultメソッドが重複
* **実装を持つのでメソッド名、引数、戻り値全部合致した場合もエラー**
* `super`で実装クラスで明示的にdefaltメソッドを参照することはOK
* 階層関係に差がある場合、同名のdefaultメソッドを使用してもOK
* より近い実装関係(直接実装関係)にあるクラスが優先

```java
public interface DHoge {
  default void log(String msg) {
    System.out.println("DHoge: " + msg);
  }
}
```
```java
public interface DHoge2 {
  default void log(String msg) {
    System.out.println("DHoge2: " + msg);
  }
}
```
```java
public class HogeImpl implements DHoge, DHoge2 {
  @Override
  public void log(String msg) {
    DHoge.super.log(msg); //エラー（Stringを持つ重複したdefaultメソッドlogはDHoge2,DHogeから継承されている）
    //IHoge.super.log(msg); //明示的に参照はOK
  }
}
```

##インターフェースと抽象クラスどっちを使う？
* インターフェース優先
* クラスはObjectクラスをrootとしいた**ツリー階層**になっている
    * インターフェース：**クラスツリーから独立**
    * 抽象クラス：**クラスツリーの一部を構成**
* 型階層は現実全ての表現ではない
    * インターフェースなら
        * 型階層から独立しているので**特定の機能を割り込ませることができる**
        * **基底クラスによらず新たなインターフェースを実装可能**
        * **多重継承可能**
    * 抽象クラスの場合、
        * **関係クラスを洗い出し全部の上位クラスとなる位置に入れる**必要
        * 途中に必要ない機能があっても**継承を拒否できない**
        * 採用するときはクラスがどのメソッドを持つか（クラスの振る舞い）より、実装に目を向けるべき
            *派生クラスでの共通処理
            * 実装処理の骨格 
        * ex: java.ioパッケージのWriterクラスでは`BufferWriter`,`OutputStreamWriter`,`PrintWriter`の基底クラスで`XWriter`枠組みを提供