多様性とは

 前回は抽象クラスやインタフェースを用いて、現実世界にあるあいまいな物事をJavaのコードとして表現できることを学びました。 ここでは、Javaの仮想世界でも物事をあいまいに捉えるための機能である多様性について説明します。 多様性はオブジェクト指向プログラミングを支える3大機能の1つで、多様性やポリモーフィズムと呼ばれます。これを上手に活用することで、とても効率よく 楽しく開発を行うことができるようになります。まずは、多様性のイメージを持つことから始めてみましょう。多様性のイメージは「あるものを、あえてざっくり捉える」ことで、様々なメリットを享受しようという機能であるといった ざっくりとしたイメージで大丈夫です。では、ざっくり捉えることのメリットは何でしょうか。これは私たちの日常生活にも多く見ることができます。 例えば、レンタカーを借りて車を運転するときのことを考えてください。厳密にいえば、初めて乗る車ですが、多くの人が問題なく運転できます。 これは、ドライバーの「ハンドルは同じだし、右ペダルがアクセル、左がブレーキ。細かいところはあれこれ違うけど、ざっくり見れば、どの車も同じだよ」という考えからきています。 このように、私たちが現実世界で楽するためにざっくりととらえる方法をプログラムでも実現する機能こそ、多様性なのです。 しかし、多様性をプログラムに落とし込む何か専用の文法があるわけではありません。実は今まで何度も用いてきた「代入の文法」を使えば、ざっくりと捉えることができるようになっています。 例を見てみましょう。

SuperHeroをざっくりとCharacterとしてとらえる

Character c = new SuperHero();
 基本的に代入式は「左辺の型と右辺の型が異なる場合はエラー」になります。しかし、この場合はエラーにならず、特例として許されています。 この判断基準は何でしょうか。これは、SuperHeroのインスタンスが箱に入っていると考えた時に、箱の見出しがそのインスタンスのことを正しく表しているかどうか で決まります。例えば、上の例ではスーパーヒーローはキャラクターの1種ですから、キャラクターが入っていますという箱の見出しでも別に矛盾がないわけなのです。 定義としては、「子クラスのインスタンスは親クラスの型に代入可能」といったところです。大事なのは、この定義を丸暗記するのではなく、イメージを可視化し、矛盾がないかをチェックすることです。 また、抽象クラスやインタフェースからインスタンスを生み出すことはできませんが、それらの型を利用することは可能です。

ざっくり捉えたものに命令を送る

 ここまでは、どのようにしてざっくり捉えるかを説明していきました。ここでは、ざっくりと捉えた時と厳密に捉えた時の違いについて説明していきます。 何かを利用する人は、それを何と捉えているかによって、利用方法が変わります。あいまいで抽象的なほど用途は限定され、具体的に捉えるほど用途が増えていきます。 これはJavaの仮想世界でもちゃんと再現されます。ここでは、CharacterとWizardを例にとって説明していきます。

Character.javaファイル

public abstract class Character{
String name;
int hp;
public void run(){}
public abstract void attack(Matango m);
}

Wizard.javaファイル

public class Wizard extends Character{
int mp;
public void attack(Motango m){

} public void fireball(Motango m){

}
}
 Wizardは魔法使いとしてattack()やfireball()のメソッドを持っているので、インスタンス化すればattackさせたりfireballを使わせたりできます。

Wizardクラスを通常の方法でインスタンス化

public class Main{
public static void main(String[] args){
Wizard w = new Wizard();
Matango m = new Matango();
w.name = "アサカ";
w.attack(m);
w.fireball(m);
}
}
 先ほど説明した通りWizardはCharacterの一種なので、Character型変数に代入することが可能です。しかし、いざ代入するとエラーが起きます。Character型の変数に格納されてるとはいえ 箱の中身のインスタンスは正真正銘のWizardインスタンスです。しかし、ざっくりととらえてしまうことによって厳密に何型のインスタンスだったかがわからなくなってしまうのです。つまり、 箱の中身は確実にWizardでありますが、Characterとざっくりととらえてしまったことで、Wizardにしかないメソッドを外部から呼び出せなくなってしまったのです。 このメソッドはもう2度と呼び出せなくなってしまうのでしょうか。そんなことはありません。Character型変数に代入された中身を強制的に「Wizardとしてとらえなおしたい場合」は 以下のように書きます。
Character c = new Wizard();
Wizard w = (Wizard)c;
 ここで(Wizard)はキャスト演算子です。これを用いることで、捉え方を変更できるのです。このように、「あいまいな型に入っている中身を厳密な型に代入する」キャストのことを ダウンキャストといい、失敗の危険性が伴います。これを防ぐために、instanceof演算子というものが用意されています。安全にキャストできるかどうかをチェックしてくれます。 例えば、次のような例を見てみましょう。
if (c instanceof SuperHero){
SuperHero h = (SuperHero)c;
h.fly();
}
 もし、cの中身がSuperHeroとみなして大丈夫なら、キャストが実行されるというプログラムです。instanceof演算子は、代入可能であるならば trueを返します。