LoginSignup
0
1

More than 3 years have passed since last update.

継承と抽象クラス、インターフェースについて

Last updated at Posted at 2021-05-10

■ 継承とは

あるクラスで定義したフィールドやメソッドを受け継ぎ、それに差分として独自のフィールドやメソッドを加えて定義し、クラスを作成することができる仕組み
extends クラス名 だけで親クラス(スーパークラス)を継承でき、子クラス(サブクラス)には差分を記述するだけでよく、追加・修正、また内容の把握や管理が楽になる

サブクラスになればなるほど具体化(特化)し、スーパークラスになればなるほど抽象化(汎化)していく( Humanクラス⇒ Athleteクラス⇒ SoccerPlayerクラス⇒ JapanTeamクラス / BasketballPlayer⇒ Lakersクラスみたいな)
特化していく毎に持っている属性情報も特化していく、例えば、Humanクラスは最低限名前、年齢、性別は持っており、Athleteクラスは身長、体重や体力などを持って、SoccerPlayerクラスではポジションや各スキル値を持ち、JapanTeamクラスではその他ウイイレで何かありそうな設定を持っている(やらないので分からない)


■ 継承のルール

・サブクラスが複数のスーパークラスを継承することはできない(多重継承の禁止)
⇒1つのスーパークラスから複数のサブクラスを定義することや、その下の孫やひ孫にあたるクラスも定義できる
・修飾子 final がついているクラスは継承できない(フィールドやメソッドについている場合はオーバーライドできない)
・「サブクラス is a 親クラス」という is-a の原則が成り立たない継承はすべきではない(できるにはできるが、オブジェクト指向と乖離し多態性が利用できなくなる)
・サブクラスをインスタンス化する場合でも、実際の処理ではまずスーパークラスのコンストラクタが呼び出される仕組みになっている
そのため、スーパークラスで引数ありのコンストラクタのみがある場合、サブクラスではエラーが発生する(引数なしのコンストラクタは使う予定がなくても用意したほうがよいというのはこういう点にもある)


■ 継承のやり方

先述した Human クラスと Athlete クラスを例に

【スーパークラス: Human クラス】

public class Human {
    private String name;
    private int age;

    public Human() {
    }

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void greet() {
        System.out.println(name + "と申します。" + age + "歳です。");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

【サブクラス: Athlete クラス】

public class Athlete extends Human {
    private int height;
    private int weight;

    public Athlete() {
    }

    public Athlete(String name, int age) {
        super.setName(name);
        super.setAge(age);
        this.height = 0;
        this.weight = 0;
    }

    public void introduction() {
        System.out.println("身長は" + height + "cm、体重は" + weight + "kgです。");
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return this.weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }
}

extends で Human クラスを継承し、差分の身長、体重フィールドを追加
コンストラクタでは、Superクラスの setter を呼び出す場合、 super. を頭につけて指定する
super.setName(name) で setter を呼び出し、引数で受ける name を設定する
追加した height、weight は初期値0としているが、特に初期値設定が必要なければ引数に指定するだけでよい

【main メソッドのクラス】

public static void main(String[] args) {
    Athlete a1 = new Athlete("レブロン・ジェームズ", 37);
    a1.setHeight(206);
    a1.setWeight(113);

    a1.greet();
    a1.introduction();
}

main メソッドのクラスで setter を呼び出し値を設定する場合には特に必要ないが、

Athlete a1 = new Athlete("レブロン", 37, 206, 113);

上記のように main メソッドのクラスで引数指定のみ行う場合、Athlete クラスのコンストラクタに変数を持たせ、this.height = height、this.weight = weight で受けた変数を private にしたフィールドに代入する必要がある


■ オーバーライド

先述した Human クラスの greet メソッドを Athlete クラスで使用する場合に限って内容を変更したい、というときには、Athlete クラス内で再定義(オーバーライド)することによって可能になる
※先述したが、final 修飾子のついたフィールドやメソッドはオーバーライドできない

Human クラスを継承し Athlete クラスになったため、スポーツの種類についても自己紹介に加えるように変更
sports フィールドを追加し

public class Athlete extends Human {
    private int height;
    private int weight;
    private String sports;

    public Athlete() {
    }

    public Athlete(String name, int age, int height, int weight, String sports) {
        super.setName(name);
        super.setAge(age);
        this.height = height;
        this.weight = weight;
        this.sports = sports;
    }

    public void greet() {
        System.out.println(sports + "選手の" + super.getName() + "、" + super.getAge() + "歳です。");
    }

    public void introduction() {
        System.out.println("身長は" + height + "cm、体重は" + weight + "kgです。");
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return this.weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public String getSports() {
        return sports;
    }

    public void setSports(String sports) {
        this.sports = sports;
    }

}
public static void main(String[] args) {
    Athlete a1 = new Athlete("レブロン・ジェームズ", 37, 206, 113, "バスケットボール");
    a1.greet();
    a1.introduction();
}

バスケットボール選手のレブロン・ジェームズ、37歳です。
となる

またオーバーライドの際には
1.他の人がコードを読む際にわかりやすい
2.Eclipse が記述ミスを検知する
という点から、@Override のアノテーションを記述したほうがよい

■ 抽象クラス / インターフェース

継承するどのサブクラスでも共通して記述する必要があると考えられるものは、基本的にスーパークラスに記述する(そのクラスが現実的に持つ属性情報は、その時点で持たせるべきという考え方)

しかし共通の属性情報であっても、個々のインスタンスによって値が異なる場合は、詳細な内容について記述できない
ならば記述しない、若しくはとりあえず内容のないメソッドなどを記述しておけばよいのではないかと考えられるが、以下

1.サブクラス作成側が定義し忘れる、若しくはオーバーライドし忘れる可能性がある

2.現時点で内容が確定できないメソッドなのか、そのまま何もしないメソッドとして定義されているのかが不明瞭

3.サブクラスのために作成した、いわば未完成の「継承専用メソッド」であるスーパークラスがインスタンス化される可能性が考えられる(サブクラス作成側が new、entends どちらもできてしまう)

これらの問題はすべて抽象クラスを利用することによって解決できる


■ 抽象クラス / 抽象メソッドとは

abstract 修飾子をクラスやメソッドに指定することで、そのクラスに対して以下の機能を付与することができる
1.内容のない(処理内容未定の)メソッドを記述できる
2.new によるインスタンス化が不可能になる
3.抽象メソッドはサブクラス、またはその下の孫クラス以下でオーバーライドしなければならない(サブクラスでエラーが出る)

【スーパークラス】

public abstract class Human {
    private String name;
    private int age;

    public Human() {
    }

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

【サブクラス】

public class Athlete extends Human {
    private int height;
    private int weight;
    private String sports;

    public Athlete() {
    }

    public Athlete(String name, int age, int height, int weight, String sports) {
        super.setName(name);
        super.setAge(age);
        this.height = height;
        this.weight = weight;
        this.sports = sports;
    }

    public void greet() {
        System.out.println(sports + "選手の" + super.getName() + "、" + super.getAge() + "歳です。");
    }

    public void introduction() {
        System.out.println("身長は" + height + "cm、体重は" + weight + "kgです。");
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return this.weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public String getSports() {
        return sports;
    }

    public void setSports(String sports) {
        this.sports = sports;
    }

}

抽象クラスの宣言は

public abstract class Human {

で class の前に abstract をつけるだけ
抽象メソッドの場合も

public abstract void greet();

でvoidの前につけるだけ、処理内容を記述しないため { } もつけない

■ インターフェース

抽象クラスがより階層を昇り曖昧なものになっていく(汎化していく)と、次第にクラス内のフィールドやメソッドが減っていき、抽象メソッドだけになっていく
フィールドを持たず、抽象メソッドのみを持つクラスをインターフェースとして扱い、抽象クラスを作成する際と記述内容が異なる
インターフェースを利用することで、継承する他クラスに対して
1.共通のメソッド群を実装するように強制できる
2.インターフェースを実装したクラスが、指定のメソッドを持っていることが保証される
という効果がある

抽象クラスが通常のクラスに対して is-a の関係であるのに対し、インターフェースは has-a の関係であるといえる(例で記述するコードはこれまで通り Human だが)

【 Human インターフェース】

public interface Human {
    void greet();

インターフェースの場合は修飾子に interface を指定する
またインターフェースとして扱われることの条件にすべてのメソッドが抽象メソッドであること、加えてインターフェースがそもそも継承されるために作成される public なものであることから、わざわざ
public abstract void greet();
と public abstract を記述しなくてよい

またフィールドを持たないこともインターフェースとして扱われる条件であるが、 final 修飾子のついたフィールドであれば宣言が許可される
インターフェースではそのままフィールドを宣言することで、自動的に public static final がつくようになっているため、例えば円周率をインターフェースで定義すると
double PI = 3.14;
という記述で
public static final double PI = 3.14
という意味になる

インターフェースを継承するクラスでは

【 Athlete クラス】

public class Athlete implements Human {

というように implements (実装)で継承する

通常のスーパークラス、サブクラスの関係では多重継承(サブクラスが複数のスーパークラスを持つ)ことは禁止されているが、インターフェースであれば多重継承が許可されている ここが抽象クラスとの1番大きな違い
※そもそも多重継承が禁止されている理由は、複数のスーパークラスで同じメソッドが定義されている場合、サブクラスでそのメソッドを呼び出した場合どちらのスーパークラスのメソッドを呼び出せばよいかわからない、という懸念に対するものであり、インターフェースはその問題に対して、もとよりすべて抽象メソッドであり内容があるものを持っていないため、禁止する理由がなくなる

そのため

public class Athlete implements Human, SportMan, Creature {

のように複数指定して継承が可能

またインターフェース同士の継承は extends を記述する
クラス ← クラス:extends
インターフェース ← クラス:implements
インターフェース ← インターフェース:extends

これら extends と implement は同時に行うことも可能

public class Athlete extends Human implements SportMan, Creature {

※また、Java8 からは " default " キーワードによる処理を持った抽象メソッドの定義が可能となっている
継承先でオーバーライドされなかった場合に、自動的にデフォルトで指定した処理内容でオーバーライドされたものとみなす機能
便利だが、先述した多重継承を禁止する理由に触れてしまう可能性も生まれることになる

0
1
0

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
0
1