Javaプログラミング入門
19. 多態性
最終更新:
javatutorial
-
view
これまでの不便な点と「多態性」のメリット
これまで「拡張による継承」や「実装による継承」を使って、親の機能を引き継いだ様々な子クラス(「勇者」や「魔法使い」など)を作れるようになりました。
しかし、これらを実際のゲームプログラムで動かそうとすると、新たな不便さに直面します。
しかし、これらを実際のゲームプログラムで動かそうとすると、新たな不便さに直面します。
例えば、ゲームの中に「勇者(Hero)」「魔法使い(Wizard)」「戦士(Fighter)」という3人の味方がいて、全員に「攻撃(attack)」の命令を出したいとします。
これまでの知識だと、それぞれ別の型として変数を作り、一人ずつ順番に命令を書く必要がありました。
Hero hero = new Hero();
Wizard wizard = new Wizard();
Fighter fighter = new Fighter();
// 一人ずつ攻撃の命令を書かなければならない
hero.attack();
wizard.attack();
fighter.attack();
今は3人だから良いですが、もし味方が100人いたらどうなるでしょうか?
100行のプログラムを書かなければならず、非常に大変です。
「10. 配列」で学んだように、同じデータ型の変数なら配列にして「9. 繰り返し(for文)」で一気に処理できますが、HeroとWizardとFighterは別々のデータ型なので、同じ配列に入れることができません。

この問題を魔法のように解決するのが、オブジェクト指向の3大要素の最後の一つ、多態性(ポリモーフィズム)です。
100行のプログラムを書かなければならず、非常に大変です。
「10. 配列」で学んだように、同じデータ型の変数なら配列にして「9. 繰り返し(for文)」で一気に処理できますが、HeroとWizardとFighterは別々のデータ型なので、同じ配列に入れることができません。

この問題を魔法のように解決するのが、オブジェクト指向の3大要素の最後の一つ、多態性(ポリモーフィズム)です。
多態性とは、簡単に言うと「親の枠組み(型)を使って、複数の異なる子クラスをひとまとめに扱う仕組み」のことです。
これを使えば、種類が違うキャラクターたちを一つの配列にまとめ、「全員攻撃しろ!」とたった数行のプログラムで命令できるようになります。

これを使えば、種類が違うキャラクターたちを一つの配列にまとめ、「全員攻撃しろ!」とたった数行のプログラムで命令できるようになります。

親の箱に子を入れる(アップキャスト)
多態性を理解するための第一歩は、変数の「箱の型」と「中身の実体」を分けて考えることです。
これまではHero hero = new Hero();のように、左側の変数の型と、右側の new で生み出す実体の型は必ず同じにしていました。
しかし、Javaには「子クラスの実体は、親クラスの型の変数(箱)に代入できる」という特別なルールがあります。
しかし、Javaには「子クラスの実体は、親クラスの型の変数(箱)に代入できる」という特別なルールがあります。
例えば、「動物(Animal)」という親クラス(またはインターフェース)があり、それを継承した「犬(Dog)」と「猫(Cat)」という子クラスがあるとします。
このとき、親であるAnimal型の変数に、DogやCatの実体を代入することができます。
サンプル:PolymorphismTest.java
このとき、親であるAnimal型の変数に、DogやCatの実体を代入することができます。
サンプル:PolymorphismTest.java
- // 親クラス型の箱に、子クラスの実体を入れることができる!
- Animal a1 = new Dog();
- Animal a2 = new Cat();
犬も猫も「動物の一種」ですよね。「動物というおおまかな分類の箱の中に、具体的な犬や猫が入っている」とイメージしてください。
このように、子クラスの型を親クラスの型として扱うことを、アップキャストと呼びます。

このように、子クラスの型を親クラスの型として扱うことを、アップキャストと呼びます。

同じ命令で違う動きをする(多態性の本質)
アップキャストをして親クラスの箱に入れた状態で、メソッドを呼び出すとどうなるでしょうか?
Animalクラスには「鳴く(speak)」というルールがあり、Dogは「ワン!」、Catは「ニャー!」とオーバーライド(上書き)しているとします。
Animal a1 = new Dog();
a1.speak(); // 「ワン!」と鳴く
Animal a2 = new Cat();
a2.speak(); // 「ニャー!」と鳴く
ここが非常に重要なポイントです。
コンピュータに対しては、どちらも「Animal(動物)よ、speak(鳴け)!」という全く同じ命令を出しています。
しかし、箱の中に入っている「実体」が犬であれば犬の鳴き方をし、猫であれば猫の鳴き方をします。
コンピュータに対しては、どちらも「Animal(動物)よ、speak(鳴け)!」という全く同じ命令を出しています。
しかし、箱の中に入っている「実体」が犬であれば犬の鳴き方をし、猫であれば猫の鳴き方をします。
このように、「同じ命令を出しても、受け取る実体によって動きが変わる(多様な状態になる)」性質のことを、多態性(ポリモーフィズム)と呼びます。


配列でまとめて処理する(多態性の真価)
親の型にまとめられるということは、「配列」が使えるようになるということです。
先ほどのRPGのキャラクターの例に戻りましょう。すべてのキャラクターが「Character」という親クラスを継承しているとします。
先ほどのRPGのキャラクターの例に戻りましょう。すべてのキャラクターが「Character」という親クラスを継承しているとします。
サンプル:PartyMain.java
- public class PartyMain {
- // 親クラス(Character)の型で配列を作る
-
- // 配列の中に、それぞれ別々の子クラスの実体を入れる(アップキャスト)
- party[0] = new Hero();
- party[1] = new Wizard();
- party[2] = new Fighter();
-
- // 繰り返し(for文)を使って、全員に一斉に命令を出す!
- for(int i = 0; i < party.length; i++) {
- party[i].attack();
- }
- }
- }
実行結果
勇者の会心の一撃!
魔法使いのファイヤーボール!
戦士のオノ攻撃!
どうでしょうか?
配列とfor文を使うことで、どれだけ人数が増えても、プログラムはたった数行のままで「全員にそれぞれの攻撃をさせる」ことができました。これこそが多態性の最大のメリットです。
配列とfor文を使うことで、どれだけ人数が増えても、プログラムはたった数行のままで「全員にそれぞれの攻撃をさせる」ことができました。これこそが多態性の最大のメリットです。
元の型に戻す(ダウンキャスト)
親の箱(Animal)に入れると便利ですが、一つだけ制限があります。
それは、「親の箱に入っている間は、親が持っているルール(メソッド)しか使えない」ということです。
それは、「親の箱に入っている間は、親が持っているルール(メソッド)しか使えない」ということです。
例えば、犬(Dog)クラスにだけ、新しく「お手(paw)」というメソッドを作ったとします。
しかし、Animal型の変数に入っている間は、コンピュータは「中身が何であるか」までは気にせず、あくまで「Animal」として扱います。
Animalには「お手」というルールがないため、呼び出そうとするとエラーになります。
しかし、Animal型の変数に入っている間は、コンピュータは「中身が何であるか」までは気にせず、あくまで「Animal」として扱います。
Animalには「お手」というルールがないため、呼び出そうとするとエラーになります。
Animal a1 = new Dog();
a1.speak(); // AnimalのルールにあるのでOK
a1.paw(); // 💡エラー! Animalのルールには「お手」がないため呼べない
これを解決して子クラス独自のメソッドを使うには、「6. 型変換」で学んだキャスト演算子()を使って、箱の型を元の具体的な子クラスに戻してあげる必要があります。
これをダウンキャストと呼びます。
これをダウンキャストと呼びます。
Animal a1 = new Dog();
// Animal型の箱から、Dog型の箱に移し替える(ダウンキャスト)
Dog d1 = (Dog)a1;
d1.paw(); // 別の箱に移したので、Dog独自のメソッドが使える! 安全にダウンキャストするための instanceof 演算子
ダウンキャストには危険が伴います。もし、中身が「猫」なのに、無理やり「犬」の箱にダウンキャストしようとすると、実行した瞬間にプログラムがエラーで強制終了してしまいます。(ClassCastExceptionというエラーが出ます)。
これを防ぐために、ダウンキャストをする前に「中身の実体が本当にそのクラスか?」を確認する安全確認の仕組みがあります。
それが instanceof(インスタンスオブ) 演算子です。
それが instanceof(インスタンスオブ) 演算子です。
変数名 instanceof クラス名
と書くと、中身が指定したクラスであればtrue、違えばfalseを返します。
サンプル:SafeCast.java
- Animal a1 = new Cat(); // 中身は猫
-
- // a1の中身は本当にDogの実体ですか?と確認する
- if (a1 instanceof Dog) {
- Dog d = (Dog)a1;
- d.paw();
- } else {
- }
多態性を使ってひとまとめにした後、一部のキャラクターにだけ特別な行動をさせたいとき(例:魔法使いだけMPを回復させるなど)に、この instanceof とダウンキャストの組み合わせが非常に役立ちます。
💡注意!
多態性を使う際は、基本的には「オーバーライド」を活用して、ダウンキャストをなるべく使わずに済むようにプログラムを設計するのが、美しくバグの少ないコードを書くコツです。
ダウンキャストは「どうしても必要なときだけの奥の手」として覚えておきましょう。
多態性を使う際は、基本的には「オーバーライド」を活用して、ダウンキャストをなるべく使わずに済むようにプログラムを設計するのが、美しくバグの少ないコードを書くコツです。
ダウンキャストは「どうしても必要なときだけの奥の手」として覚えておきましょう。
コラム
Java 16からの便利な新構文「パターンマッチング」
本編で、安全にダウンキャストをするための instanceof 演算子について学びました。
しかし、これまでの書き方だと「①中身が犬か確認する」「②犬の箱(変数)を用意して、ダウンキャストして移し替える」という2つの手順を必ず書かなければなりませんでした。
しかし、これまでの書き方だと「①中身が犬か確認する」「②犬の箱(変数)を用意して、ダウンキャストして移し替える」という2つの手順を必ず書かなければなりませんでした。
// これまでの書き方
if (a1 instanceof Dog) {
Dog d = (Dog)a1; // わざわざもう一度、(Dog)と書いて型変換する必要があった
d.paw();
}
「せっかく犬だと確認できたのだから、わざわざもう一度 (Dog) って書かなくても、自動で変換してよ!」と思いませんか?
実は、Java 16から、まさにその願いを叶えるパターンマッチングという新しい文法が追加されました。
実は、Java 16から、まさにその願いを叶えるパターンマッチングという新しい文法が追加されました。
// Java 16以降の新しい書き方
if (a1 instanceof Dog d) {
d.paw(); // 確認と同時に、すでにDog型の変数「d」が使えるようになっている!
}
このように、instanceof クラス名 の後ろに新しい変数名(この例では d )を書き足すだけで、「もし中身がDogだったら、ダウンキャストまで自動で済ませて、すぐ使えるように変数 d に入れておくね」という処理をコンピュータがやってくれるようになりました。
無駄なプログラム(記述量)が減り、書き間違いによるエラーも防げるため、最新のJava開発現場ではこの新しい書き方が主流になってきています。
ぜひ覚えておきましょう!
無駄なプログラム(記述量)が減り、書き間違いによるエラーも防げるため、最新のJava開発現場ではこの新しい書き方が主流になってきています。
ぜひ覚えておきましょう!









