ほぷしぃ

Java言語入門 〜C言語を学んだ君へ〜

[第9回] 継承

[1] 継承の基本

継承はオブジェクト指向における重要な概念の1つです。
ここでしっかり基礎を覚えてください。

継承とは

継承とは、「あるクラス」の機能を引き継いで、「新しいクラス」を作成するための仕組みです。

「あるクラス」をスーパークラス、「新しいクラス」をサブクラス

といいます。これについては、第7回「オブジェクト指向プログラミング」で説明しました。
ここではもう少し踏み込んで、継承関係についての説明をします。

「AはBの一種」である。

この関係が成り立つ時、AはBを継承できます。
例えば、「人間は哺乳類の一種」です。他に「犬は哺乳類の一種」です。
よって、人間と犬は哺乳類を継承できます。

哺乳類の分類

ここで継承を行う上での注意点があります。
「AはBの一種」の関係からわかるように、AはBより特化(具体化)しています。
そのため、「哺乳類が人間を継承する」というような劣化(抽象化)の継承関係は行わないでください。
継承を学ぶ前にこのような注意をしたのは、むやみに継承してほしくないためです。
継承を使えば、「作業効率の向上」「可読性の向上」などが期待できますが、誤って使えば、逆の効果に陥る危険性もあります。
そのため、継承を行う場合は「AはBの一種」を思い浮かべて、正しい継承が行うことを心掛けてください。

継承の基本形

継承を行うために必要なキーワードがextendsです。
継承元のクラスの定義時に次のようにして使います。

継承の書式

class A extends B {
:
}

これで「AはBを継承する」ことになります。
このときのAがサブクラス、Bがスーパークラスとなります。
では、具体例として、哺乳類クラスと人間クラスを作成してみましょう。
人間は哺乳類の一種なので、哺乳類クラスを継承すればいいことになります。
哺乳類がスーパークラスで、人間がサブクラスです。
では、図で関係を見てみましょう。
継承の例

少し簡単な例ですが、このようなクラス関係をソースに置き換えてみます。
また、一般的にこのような図をクラス図と呼びます。

サンプルプログラム

class Mammal {
    private String name;
    private int age;

    public Mammal(String name, int age) {
        System.out.println("Mammalコンストラクタの呼び出し");
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(age + "歳の" + name + "が食事をします");
    }
}
class Human extends Mammal {
    public Human(String name, int age) {
        super(name, age);
        System.out.println("Humanコンストラクタの呼び出し");
    }
}
public class Java09_01 {
    public static void main(String args[]) {
        Human human = new Human("太郎", 10);
        human.eat();
    }
}

実行結果

継承の例

少しわかりにくかったと思いますが、重要な点は以下の3つです。

・Mammalクラスに「名前」、「年齢」、「食べる」を定義していること
・Humanクラスに「メンバ変数」や「メソッド」を定義していない
・Humanクラスを作成したのに、Mammalクラスの機能(eat())が使えること

以上の点が確認できたら完璧です。これが「継承」というものです。
HumanクラスはMammalクラスを継承した時点で、Mammalクラスの機能が使えます。
1つ継承を使う時に注意点があります。

多重継承はすることができません。

つまり、"class A extends B, C"とすることができません。
別に複数できてもいいのでは?と思いますが、きちんと理由があります。それは第10回で説明があります。
他に「super」などわかりにくい所もあったと思いますが、それについては次の項目で説明します。

[2] コンストラクタの呼び出し

オブジェクトを生成するとき、コンストラクタは必ず呼び出されます。
仮に、コンストラクタを定義していなければ、
JVMによって引数のないコンストラクタが自動的に作成されます。
このコンストラクタをデフォルトコンストラクタといいます。
ここで、話を継承に戻しますが、継承したクラスをインスタンス化する場合、

そのクラスのコンストラクタより先に、スーパークラスのコンストラクタを呼び出さなくてはいけない

という決まりがあります。そのため、上のHumanクラスのコンストラクタの初めに「super」と書きました。
これは、スーパークラスのことを指しています。
前回説明した「this」はクラス自身でしたが、それに似たものです。
つまり、「super(name, age)」によって、
スーパークラスのコンストラクタを呼び出しています。その結果、

サブクラスのインスタンス化(メインの処理)
     ↓
スーパークラスのコンストラクタの呼び出し
     ↓
サブクラスのコンストラクタの呼び出し

という順番で処理が行われます。実行結果がその証拠です。
なお、スーパークラスとサブクラスのコンストラクタがお互いに引数なしの場合、
「super」と書かなくても自動的に引数なしのコンストラクタが呼び出されます。
ですが、どのコンストラクタを呼び出したかわかりやすくするために、
「super」を書いた方が良いでしょう。

[3] オーバーライド

オーバーライドについて説明します。
これによって、より継承としての質が上がります。

オーバーライドとは

オーバーライドとはスーパークラスのメソッド内容の書き換えです。
これまでの継承はただスーパークラスの機能を引き継ぐだけでした。
しかし、サブクラスではさらに特化した機能に変更したい場合があります。
例えば、前のMammalクラスのeatメソッドは「食事をします」という抽象的なものでした。
それは、哺乳類によって「食べ物」や「食べ方」が違うため、具体的な処理ができないためです。
しかし、人間なら「箸を使ってご飯を食べる」という具体的な処理ができます。
では、実際にオーバーライドによって、Humanクラスを変更します。

サンプルプログラム

class Mammal {
    private String name;
    private int age;

    public Mammal(String name, int age) {
        System.out.println("Mammalコンストラクタの呼び出し");
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(age + "歳の" + name + "が食事をします");
    }
}
class Human extends Mammal {
    public Human(String name, int age) {
        super(name, age);
        System.out.println("Humanコンストラクタの呼び出し");
    }
    // オーバーライド
    public void eat() {
        System.out.println("箸を使ってご飯を食べる");
    }
}
public class Java09_01 {
    public static void main(String args[]) {
        Human human = new Human("太郎", 10);
        human.eat();
    }
}

実行結果

実行結果

オーバーライドは、書き変えたいメソッドと同等なものを、もう一度サブクラスで定義し直すことで実現します。
今回は「eat」メソッドを書き換えました。より人間らしい食事になったと思います。
人によって箸を使わないとか、そういう苦情はやめてください。

super

オーバーライドについては説明しました。
しかし、前の方法には不十分な点があります。
それは機能の追加です。前回は機能を書き直していました。
そこで、「super」を使って機能の追加を行います。「super」とはスーパークラスを指します。
つまり、Humanクラスで「super.eat()」と書けば、Mammalクラスのeatメソッドを呼び出すことができます。
これを使って、機能の追加が実現できます。以下のようにHumanクラスを変更します。

サンプルプログラム

class Mammal {
    private String name;
    private int age;

    public Mammal(String name, int age) {
        System.out.println("Mammalコンストラクタの呼び出し");
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(age + "歳の" + name + "が食事をします");
    }
}
class Human extends Mammal {
    public Human(String name, int age) {
        super(name, age);
        System.out.println("Humanコンストラクタの呼び出し");
    }
    // オーバーライド
    public void eat() {
        super.eat();
        System.out.println("箸を使ってご飯を食べる");
    }
}
public class Java09_01 {
    public static void main(String args[]) {
        Human human = new Human("太郎", 10);
        human.eat();
    }
}

実行結果

実行結果

実行結果より、機能が追加できたことがわかります。
なお、スーパークラスのコンストラクタの呼び出しと異なり、
スーパークラスのメソッドの呼び出しは場所を選ばないので、
好きな場所で呼び出すことができます。

[4] まとめ

ここで、このページで学習したことのおさらいをしていきましょう。

継承

・継承とは、クラスを引き継いで新しいクラスを作る仕組み
・class 引き継ぎ先(サブクラス) extends 引き継ぎ元(スーパークラス){..}

super

・スーパークラスのコンストラクタ、またはメソッドを呼び出す仕組み

オーバーライド

・スーパークラスのメソッド内容の書き換え
・同じメソッド書くことで、内容を変えることが可能
・オーバーロードと言葉が似ているが違うので要注意
・オーバーロードは、メソッドが同じで、引数の型、個数が違えば同じメソッド名を作れる仕組み

UML

継承の説明で、ある図を見せました。オブジェクト指向によるシステム開発では、
そのオブジェクト間のやりとりを言葉で伝えることになりますが、
プログラムを見て判断するというのは、情報が多すぎて大変です。
そこで、このような図を使って伝えやすくします。
これにより、はるかにわかりやすく伝えることができます。
これをUMLといいます。上で使われている図はクラス図と呼ばれていて、
クラス関係を示すときによく使われる図なのです。
また、他のUMLには動作を中心としたものなどさまざまあります。
このコンテンツではUMLについてこれ以上の説明を行いませんが、
Javaプログラムを一通り学んだらUMLを学んでみると良いでしょう。
まだまだ継承がこのあとも続きます。
キャスト、final、アクセス修飾子を次に学びましょう。



第8回へ ページのトップへ 次のページへ