【継承】Java入門〜後編〜

前書き

前回の記事〜オブジェクト指向
melheaven.hatenadiary.jp

前回はオブジェクト指向の基礎について学びました。今回はクラスが複数存在するときに、親子関係(継承)について学んでいきます。

継承

作成したクラスのうち、似通っているクラスが存在してきます。しかし一部だけ異なる機能を持ち合わせていた時、新たにクラスを作成する事が煩雑であると感じた時に継承の概念が役に立ちます。

ここでは親クラスと子クラスを定義します。親クラスと子クラスの関係を継承関係と呼びます。親クラスを「スーパークラス」、派生した子クラスを「サブクラス」とも呼びます。前回までの例から倣うと、親クラスとして「スポーツクラス」、子クラスとして「競馬クラス」とします。競馬は「競馬」でもありますが、同様に「スポーツ」でもありますから、この定義は理解できると思います。

重要な条件は「一つの子クラスが複数の親クラスから継承させる事は許されない」事です。このルールを守って継承関係のクラスを考えていきます。

class Sports {
//    親クラス
    protected String name;
    protected String place;

   // コンストラクタ
    public Sports(){}

    // final void name() -> オーバーライドを禁止
    void name() {
        System.out.println("スポーツ名は" + name + "です。さぁ始まります!<親クラス>");
    }
}

public class Horse extends Sports{
  // 子クラス
  // オーバーライド
  void name(){
    // 親クラスのname()と異なる内容
  }
}

子クラスが親クラスから継承する際に、クラス名の後ろに"extends 親クラス"のように定義します。子クラスは親クラスを継承したので、親クラスには存在しない「差分」となる情報を子クラスの内部に記述します。

基本的には継承元の親クラスのメンバを活用しますが、子クラスで同名の独自メンバを定義したい場合は、子クラス側で再宣言して上書きをする事が可能で、これをオーバーライドと呼びます。ただしオーバーライドを過剰に許可してしまうと、複雑で可読性の低いコードになります。そこでオーバーライドを禁止する為に、親クラスのメンバ定義にて"final"を追加することも可能です。

superで親インスタンスから呼び出し

親クラス(Sportsクラス)にて定義したフィールド"name"やメソッド"name()"を子クラスにて呼び出したいときは、"super"を活用します。また親クラスのコンストラクタを呼び出したいときは、子クラスの先頭に"super()"を配置します。

public class Horse extends Sports{
        // 親クラスのコンストラクタを呼び出し
        super();
        // 親クラスのフィールド"name"の更新
        super.name = "競馬";
        //  親クラスのメソッド"name()"を呼び出し
        super.name();
}

抽象クラス(abstract)

継承関係のクラスを用意した時に発生する問題点が存在します。本来、クラスの利用には①new()によりインスタンスの生成、②extends により継承の2パターン存在します。ただここには問題が生じます。

上記の例で言うと、コード作成者が「親クラス(Sportsクラス)は継承に利用してね!」という思いを込めて親クラス(Sportsクラス)を作成したとします。ただその意図を汲んでいない利用者が「親クラスから直接newを用いてインスタンスを生成してしまう」可能性もあります。これは利用者が悪いと言うわけではなく、こういった事故が発生してしまう可能性は当然存在しているわけです。

じゃあどうやって解決するの?

ここで抽象クラスです。

抽象クラスは以下のように定義します。

abstract class Sports {

  // 抽象メソッド
  public abstract void place();
}

public class Horse extends Sports  {

・・・

 public void place() {
        System.out.println("<場所> : 場所は" + this.place + "です。");
    }
}
Sports s = new Sports();

→ Sportsはabstractです。「インスタンスを生成できません」

親クラスに"abstract"を付与することで抽象クラスを宣言できます。抽象クラスでは上記で述べた「①new()によりインスタンスの生成」の利用が禁止されます。その為、上記で述べたような事故が発生しなくなります。

そして抽象メソッドを定義していますが、「子クラスにて必ずplaceメソッドを定義しなければならない(オーバーライド)」の義務が課されます。これにより「クラスを継承したものの、オーバーライドし忘れる」と言う事故も防ぐ事ができます。

抽象メソッドは「継承元では具体的に明記しなくていいけど、継承先のクラスでは必ず具体的な処理を明記してね」といった約束の意味が込められています。

インターフェース

インターフェースとはクラスに含まれているメソッドの内部処理を実装せずに、変数とメソッド型のみを定義できます。
イメージするなら「特別な抽象クラス」であり、特別な抽象クラスに定義されたメソッドは、「継承する子クラスではメソッドは必須だけど、メソッドの実装については自由でいいよ」とされています。

上記のインターフェースの特徴により、最も効果的なのは多重継承の実現です。基本的にJavaでは複数の親クラスからの継承は認められていませんが、インターフェースでは「実装の衝突」が発生しないため、許可されています。

package com.example.helloworld.horsedir;

public interface Animal {
    void run();
    void stop();
}

public class Horse extends Sports implements Animal {
     //    メソッド
    public void run(){
        System.out.println("走ってます!!!!");
    }
    public void stop(){
        System.out.println("終了しました!!!!");
    }
}

作成したインターフェースから継承するとき"implements インターフェース名"を宣言します。インターフェースに宣言されたメソッドはpublic abstract、フィールドはpublic static finalと自動的に設定されます。インターフェースを生成するときは、「必須のメソッドは存在するが内部の実装に関しては自由なオブジェクト」「複数のクラスから継承をしたいとき」に有効そうです。