Java入門 STEP16|ポリモーフィズムを学ぼう

karin@taishi@felmia

ポリモーフィズムとは?

STEP15では、インターフェースを使って、異なるクラスに共通のルールを持たせる方法を学びました。

今回は、ここまで学んできた継承やオーバーライドを使って、複数のオブジェクトを共通の型として扱う「ポリモーフィズム」について学びます。

例えば、DogとCatがどちらもAnimalを継承しているとします。

通常であれば、

Dog dog = new Dog();
Cat cat = new Cat();

のように、それぞれの型でオブジェクトを扱えます。

しかし、継承関係がある場合は次のような書き方もできます。

Animal animal = new Dog();

左側はAnimal、右側はDogになっています。

型が違うので少し不思議に見えるかもしれませんが、DogはAnimalを継承しているため、このように扱うことができます。

ポリモーフィズムとは?

同じ型として扱いながら、実際のオブジェクトに応じて異なる動きをさせることができる仕組みです。

日本語では「多態性」とも呼ばれます。

ルミィ
ルミィ

DogなのにAnimal型に入れられるんだ!
継承したクラスだからできるんだね♪

親クラスの型で子クラスを扱う

実際にコードで確認します。

まず、親クラスとなるAnimalを作ります。

public class Animal {

    public void makeSound() {
        System.out.println("鳴きます");
    }
}

次に、Animalを継承したDogを作ります。

public class Dog extends Animal {

    @Override
    public void makeSound() {
        System.out.println("ワン!");
    }
}

実行用のPolymorphismSampleを作ります。

public class PolymorphismSample {

    public static void main(String[] args) {

        Animal animal = new Dog();

        animal.makeSound();
    }
}

実行結果

ワン!

注目するのは、

Animal animal = new Dog();

です。

変数の型はAnimalですが、実際に作っているオブジェクトはDogです。

そして、

animal.makeSound();

を実行すると、DogでオーバーライドしたmakeSound()が呼ばれます。

そのため、「鳴きます」ではなく「ワン!」と表示されます。

Catでも同じように扱える

CatもAnimalを継承してみます。

public class Cat extends Animal {

    @Override
    public void makeSound() {
        System.out.println("ニャー!");
    }
}

DogとCatは、どちらもAnimalとして扱うことができます。

PolymorphismSampleを次のように変更します。

public class PolymorphismSample {

    public static void main(String[] args) {

        Animal dog = new Dog();
        Animal cat = new Cat();

        dog.makeSound();
        cat.makeSound();
    }
}

実行結果

ワン!
ニャー!

どちらの変数も型はAnimalです。

しかし、dogにはDogのオブジェクト、catにはCatのオブジェクトが入っています。

そのため、同じ、

makeSound();

を呼び出しても、実際のオブジェクトに合わせた処理が実行されます。

同じ型・同じメソッドでも動きが変わる

Animal型として扱っていても、実際のオブジェクトがDogならDogの処理、CatならCatの処理が実行されます。

これがポリモーフィズムの大切な特徴です。

ルミィ
ルミィ

どっちもAnimalなのに、ちゃんと「ワン!」と「ニャー!」に分かれるんだね!

配列にまとめてみよう

共通の型として扱えると、複数のオブジェクトをまとめて扱いやすくなります。

STEP8で学んだ配列を使ってみます。

public class PolymorphismSample {

    public static void main(String[] args) {

        Animal[] animals = {
            new Dog(),
            new Cat()
        };

        for (Animal animal : animals) {
            animal.makeSound();
        }
    }
}

実行結果

ワン!
ニャー!

DogとCatは別のクラスですが、どちらもAnimalを継承しています。

そのため、Animal[]という1つの配列にまとめることができます。

さらに、

animal.makeSound();

という同じコードだけで、それぞれのオブジェクトに合った処理が実行されます。

もし動物の種類が増えても、同じように扱うことができます。

例えばBirdを追加しても、

Animal[] animals = {
    new Dog(),
    new Cat(),
    new Bird()
};

のように追加できます。

DogならDog用、CatならCat用、BirdならBird用のmakeSound()が実行されるため、呼び出す側で動物ごとに処理を分ける必要がありません。

ルミィ
ルミィ

種類が増えても、呼び出す側は同じmakeSound()でいいんだ!
これならまとめて扱いやすいね♪

使えるメソッドは変数の型で決まる

ここはポリモーフィズムで間違えやすい部分です。

Dogに、Dogだけが持つbark()を追加したとします。

public class Dog extends Animal {

    @Override
    public void makeSound() {
        System.out.println("ワン!");
    }

    public void bark() {
        System.out.println("犬が吠えています");
    }
}

次の変数はAnimal型です。

Animal animal = new Dog();

実際のオブジェクトはDogですが、

animal.bark();

とは書けません。

Animalクラスにはbark()が定義されていないためです。

呼び出せるメソッドにも注意

Animal animal = new Dog();

の場合、変数animalから呼び出せるメソッドは、基本的にAnimal型から確認できるものです。

実際のオブジェクトがDogだからといって、Dog独自のメソッドをそのまま呼び出せるわけではありません。

この違いは、ポリモーフィズムを使うときに重要になります。

インターフェースでも使える

ポリモーフィズムは、クラスの継承だけでなく、STEP15で学んだインターフェースでも利用できます。

STEP15のMovableを例にします。

public interface Movable {

    void move();
}

DogとBirdで実装します。

public class Dog implements Movable {

    @Override
    public void move() {
        System.out.println("犬が走ります");
    }
}
public class Bird implements Movable {

    @Override
    public void move() {
        System.out.println("鳥が飛びます");
    }
}

実行用クラスは、前半のサンプルと名前が重ならないようにInterfacePolymorphismSampleとします。

public class InterfacePolymorphismSample {

    public static void main(String[] args) {

        Movable dog = new Dog();
        Movable bird = new Bird();

        dog.move();
        bird.move();
    }
}

実行結果

犬が走ります
鳥が飛びます

DogとBirdは別のクラスですが、どちらもMovableを実装しています。

そのため、Movableという共通の型で扱うことができます。

ここでSTEP15のインターフェースが、単に「メソッドを作らせるルール」だけではなく、共通の型としても使えることが分かります。

ポリモーフィズムを使うメリット

ポリモーフィズムを使うと、クラスごとに処理を書くのではなく、共通の型を使ってまとめて処理できるようになります。

例えば、

Dog

Cat

Bird

と種類が増えても、すべてAnimalとして扱えるのであれば、

for (Animal animal : animals) {
    animal.makeSound();
}

という同じ処理で呼び出せます。

実際にどの処理が動くのかは、それぞれのオブジェクト側に任せられます。

プログラムが大きくなり、扱うクラスが増えてくるほど、この考え方が役立つ場面も増えてきます。

ポリモーフィズムの考え方

共通の型としてまとめて扱い、同じメソッドを呼び出す。

実際の処理は、それぞれのオブジェクトに合わせて実行される。

よくある間違い

左右の型が違うのでエラーだと思ってしまう

Animal animal = new Dog();

は正しい書き方です。

DogがAnimalを継承しているため、Animal型として扱うことができます。

親クラス型から子クラス独自のメソッドを呼ぼうとする

Animal animal = new Dog();

animal.bark();

bark()がDogにしかない場合、このままでは呼び出せません。

実際に入っているオブジェクトだけでなく、変数がどの型として扱われているかにも注意しましょう。

オーバーライドとポリモーフィズムを別々に考えてしまう

STEP13で学んだオーバーライドは、今回のポリモーフィズムと深く関係しています。

同じmakeSound()を呼び出しても、

Dogなら「ワン!」

Catなら「ニャー!」

となるのは、それぞれのクラスでmakeSound()をオーバーライドしているためです。

これまで学んだ内容が、ここでつながってきます。

STEP16のまとめ

STEP16のポイント

  • ポリモーフィズムは日本語で「多態性」と呼ばれる
  • 親クラスの型で子クラスのオブジェクトを扱える
  • 同じ型・同じメソッドでも、実際のオブジェクトによって処理が変わる
  • オーバーライドと組み合わせることで、それぞれに合った処理を実行できる
  • 共通の型を使うことで、複数のオブジェクトをまとめて扱いやすくなる
  • インターフェースも共通の型として利用できる
  • 親クラス型の変数から、子クラスだけが持つメソッドをそのまま呼び出すことはできない

最初は、「共通の型で扱って同じメソッドを呼んでも、実際のオブジェクトによって動きが変わる」と理解できれば大丈夫です。

ルミィ
ルミィ

STEP16クリア!
継承やオーバーライドが、ここでひとつにつながった感じがするね♪

次のSTEPへ

次のSTEPでは、複数のデータを扱うときによく使われる「List・ArrayList」について学びます。

STEP8では配列を使って、複数の値をまとめて管理する方法を学びました。

ただし配列は、作成するときに要素数を決める必要があります。

実際のプログラムでは、

「あとからデータを追加したい」

「不要になったデータを削除したい」

という場面もあります。

そんなときに使いやすいのがListやArrayListです。

配列との違いも確認しながら、データを追加・取得・削除する基本的な操作を学びます。

→ STEP17「List・ArrayListを学ぼう」

学習内容を確認したいときは、Java入門の一覧から各STEPを確認できます。

→ Java入門|初心者が基礎から学べるJava学習ガイド

記事URLをコピーしました