ほぷしぃ

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

[第9回] 継承

[1] 継承とポリモーフィズム

これまでに「継承」、「オーバーライド」、「キャスト」について説明しました。
これでポリモーフィズムを実現できます。
オーバーロードとは異なる方法を説明します。

設計

設計と言ったら大げさですが、どのようなものを作るかを考えるということです。
ここでは、哺乳類の継承を例にポリモーフィズムを実現させます。
まず、「哺乳類」には「犬」、「猫」、「人間」がいます。
これらの共通点は「食べる」ことです。
(他にもたくさんありますが、1つだけにします)
「食べる」ことは同じですが、具体的に食べるものはそれぞれ異なります。

ポリモーフィズムの例
そこで、スーパークラスにある「食べる」には何も書きません。
継承をするクラス(人間、犬、猫)に何を食べるのか決めます。
では、食べるものはそれぞれ何にしましょうか。
犬は「ドッグフード」、猫は「キャットフード」、人間は「カレー」にしましょう。
この食べる部分には「オーバーライド」を使います。
これで、各4つのクラスを作成します。
次の項目でこのクラス図を元にプログラムを作成し、ポリモーフィズムを実現させます。

作成

実際に作成します。ソースは次のようになります。

サンプルプログラム

class Mammal {
    public void eat() {
    }
}
class Dog extends Mammal {
    public void eat() {
        System.out.println("ドッグフードを食べます");
    }
}
class Cat extends Mammal {
    public void eat() {
        System.out.println("キャットフードを食べます");
    }
}
class Human extends Mammal {
    public void eat() {
        System.out.println("カレーを食べます");
    }
}
public class Java09_07 {
    public static void main(String args[]) {
        Mammal mammal[] = new Mammal[3];
        mammal[0] = new Dog();    // 犬生成
        mammal[1] = new Cat();    // 猫生成
        mammal[2] = new Human();    // 人間生成

        for (int i = 0; i < mammal.length; i++) {
            mammal[i].eat();    // まとめてeat()を呼び出す
        }
    }
}

実行結果

実行結果
メインで同じeatメソッドを3回呼び出しています。
しかし、各オブジェクトに定義されているeatメソッドの処理は異なります。
それが実行結果に反映されています。
ポリモーフィズムが実現できたことがわかると思います。
ポリモーフィズムとは、同じ名前で異なる処理を行うことです。

[2] 抽象クラスと抽象メソッド

通常のクラスとメソッドとは少し違ったクラスとメソッドについて説明します。
それは、抽象クラス抽象メソッドです。
継承によるポリモーフィズムを実現する手法になるため、しっかり覚えてください。

ポリモーフィズムの実現

ポリモーフィズムは、第8回「クラス」で説明しました。
同じ名前で異なる処理を行うことだと説明しました。
つまり、すでに"Java09_07のプログラム"で、継承によるポリモーフィズムを実現しています。
(eat()メソッドをそれぞれdog,cat,humanで、同じメソッド名でも処理が異なっています。)
では、ポリモーフィズムを実現するための手法の抽象クラス、抽象メソッドとは何なのでしょうか。

まず、Java09_07のプログラムでポリモーフィズムは実現できました。
それは小規模のソースでありすべてのクラスを「同一人物」が作成したソースだからです。
Java09_07の例では、「eatメソッドをオーバーライドするぞ!」と決めていたから実現できたのです。
では、もしクラスごとに作成する人が異なったらどうなるでしょうか。
Mammalクラスを継承しても、中にはeatメソッドをオーバーライドせずに、「taberuメソッド」という全く別のメソッドを作る人がいるかもしれません。
他に、先ほどのように「決まり事」があったとしても、「eetメソッド」のように誤って書く人もいるでしょう。
このような場合、ポリモーフィズムは実現できなくなります。

以上の問題を解決するために、必ずオーバーライドしなくてはいけないeatメソッドを作る必要があります。
そうすれば、誤ったメソッドを作る人が減り、ポリモーフィズムが実現しやすくなります。
これを実現する目的としてあるのが、抽象クラスと抽象メソッドなのです。その方法について次の項目で説明します。

abstract

abstractとは「抽象的」な意味を持つキーワードで、何も書いていないメソッドを作るために使います。
このようなメソッドを抽象メソッドといい、次のように書きます。

abstract void method();

戻り値の前に「abstract」を付けて、さらに「{}」の代わりに「;」を付けます。
まさに「何も書いていないメソッド」の出来上がりです。
そして、この「抽象メソッド」を「1つ以上」含むクラスを抽象クラスといい、次のように書きます。

abstract class A {
    abstract void method(); // 抽象メソッド
}

クラスの前に「abstract」を付けます。
抽象クラスとメソッドを使う時には以下のことに気をつけて使います。

抽象メソッドを作る時には、必ずクラスにも"abstract"を書くこと(抽象クラスにすること)
抽象クラスはインスタンス化しないこと(できません)
抽象クラスを継承したら必ず抽象クラスにある抽象メソッドをオーバーライドすること

理由は「何も書いていないメソッド(抽象メソッド)」があり、
継承によってオーバーライドすることを前提にしているためです。

abstractの制限

具体的にどのようなことが、制限されるかを説明します。
Java09_07プログラムのソースを少し変更しました。次のソースを見てください。

サンプルプログラム

// 抽象クラス
abstract class Mammal {
    // 抽象メソッド
    public abstract void eat();
}

// 正しくオーバーライドしたクラス
class Dog extends Mammal {
    public void eat() {
        System.out.println("ドッグフードを食べます");
    }
}

// 新しくtaberuメソッドを作ったクラス(コンパイルエラー)
class Cat extends Mammal {
    public void taberu() {
        System.out.println("キャットフードを食べます");
    }
}
public class Java09_08 {
    public static void main(String args[]) {
        Mammal mammal[] = new Mammal[3];
        mammal[0] = new Dog();
        mammal[1] = new Cat();
        mammal[2] = new Mammal();    // 抽象クラス作成(コンパイルエラー)

        for (int i = 0; i < mammal.length; i++) {
            mammal[i].eat();
        }
    }
}

実行結果

実行結果
上のソースでは、2つのコンパイルエラーが起きています。
1つは「Catクラス」で「eatメソッド」をオーバーライドしていないからです。
eatメソッドは抽象メソッドのため、オーバーライドしないと、コンパイルエラーが起きます。
もう1つは、「Java09_06クラス」で抽象クラスをインスタンス化しようとしているからです。
抽象クラスはインスタンス化できません。そのため、コンパイルエラーが起きます。
以上のように、誤った使い方があると、コンパイラが教えてくれるため、
バグの起きにくいソースを作ることができるようになります。

[3] Objectクラス

Objectクラスについて説明します。
このクラスは「継承」に関わっており、Javaプログラミングをする上で、覚えておきたいクラスの1つです。

Objectクラスとは

これまで継承するには、「extends」をすると説明しました。
では、「extends」を付けないクラスは、他のクラスと継承関係のない、独立したクラスになるのでしょうか。
実は「extends」を付けなくても、継承は行われます。
その継承元のクラスがObjectクラスです。

このObjectoクラスは継承関係において、すべてのクラスの頂点に立つクラスです。

今まで使用した「Stringクラス」や「Systemクラス」、その他「自作クラス」も、
元を辿ればObjectクラスを継承しています。

Objectクラスのメソッド

Objectクラスはすべてのクラスの頂点にあるため、特殊なメソッドを持っています。
ここでは、その一部を紹介します。

boolean equals

前回の説明で使用した「equals」です。
これは、2つのオブジェクトが等しいかどうかを調べるメソッドです。
前回は、Stringクラスに使用しましたが、実は「Objectクラス」のメソッドなのです。
そのため、他のクラスすべてに「equalsメソッド」があります。
ただし、自作クラスなどで「オーバーライド」をしていない場合、
正しく動作しない場合があります。

Class getClass

見たことない戻り値だと思いますが、これは「Classクラス」です。
つまり、これはClassクラスを返すメソッドです。
Classクラスとは、そのオブジェクトに関する情報を保持します。
例として、Classクラスのメソッドには「String getName()」があります。
このメソッドを使うとそのクラスの名前を返します。次のソースを見てください。

サンプルプログラム

public class Java09_09 {
    public static void main(String args[]) {
        String str = "文字列です";
        System.out.println(str.getClass().getName());
    }
}

実行結果

実行結果
このように、オブジェクトがStringクラスであると教えてくれます。

Objectクラスとキャスト

キャストについてはすでに説明しました。
そこでは、キャストは「継承関係」がないと行えないと説明しました。
ところが、Objectクラスはすべてのクラスのスーパークラスです。
ということは、どのようなクラスを作っても、必ずどこかで「継承関係」が生まれます。
つまり、どのようなクラスでもObjectクラスにキャストができるということです。
次のソースを見てください。

サンプルプログラム

class X {
}

class Y {
}

public class Java09_10 {
    public static void main(String args[]) {
        Object obj[] = new Object[2];
        obj[0] = new X();
        obj[1] = new Y();
        for (int i = 0; i < obj.length; i++) {
            System.out.println(obj[i].getClass().getName());
        }
    }
}

実行結果

実行結果
「extends」を使わずに作成した「Aクラス」と「Bクラス」のオブジェクトが、同じ配列(変数)に格納できます。
これは、Objectクラスの配列のため、どのようなクラスも格納することができるということです。
これを用いると、1つの配列で異なるクラスをすべて管理することも可能です。
ただし、このように何でも1つの配列で管理すると、
どのオブジェクトが入っているかがわかりにくくなるため、
使うときは十分注意してください。

[4] まとめ

それでは、このページで学んできたことを簡単におさらいしておきましょう。

オーバーライド

スーパークラスにあるメソッドをサブクラスで変更することができる。

abstract

「abstract」がついているクラスを抽象クラスと言う。
「abstract」がついているメソッドを抽象メソッドと言う。
「abstract」は継承した時のメソッド変更の誤りを防ぐ役割がある。
「abstract」メソッドは必ずabstractクラスに所属している。
「abstract」クラスを引き継いだら、サブクラスでは必ずabstractのメソッドを実装する。

Objectクラス

Objectクラスは全てのクラスの頂点に立つクラスであり、
全てのクラスはこのObjectを継承している。
boolean equals(Object obj)やClass getClass()等のメソッドを持っている。



前のページへ ページのトップへ 第1問-問題へ