オーバーライド


動的メソッドディスパッチメカニズム

前章では、オブジェクト指向の主要三概念の一つ「継承」を学びました
今回はさらに、オブジェクト指向主要三概念の一つポリモーフィズムを学習しましょう

ポリモーフィズム(polymorphism)は、ずばり訳すと「多様性」です
よく「一つのインターフェイス、複数の実装」という表現がありますが、そのとおりなのです
つまり、一つのメソッド名に複数の意味を持たせるプログラム技術をポリモーフィズムといいます

Java言語にはポリモーフィズムを実現する二つの技術があります
一つは、もうすでにやっている「メソッドのオーバーロード」です
今回はさらに注目すべきJava言語のポリモーフィズムの特徴といえるオーバーライドを学びましょう

さて、オーバーライドを学習する前に、これまでより明確に「インスタンスとオブジェクト」について知る必要があります
C++言語などでは、明示的にコンストラクタを呼び出す必要はありませんでした
しかし、Java言語ではオブジェクト生成時にコンストラクタを呼び出しています

cls_name objRef = new constructor(args);

このときのcls_nameは変数に格納するためのデータ型です
objRefは、cls_nameを参照するための変数です
constructor(args)は、クラスのインスタンスの参照を変数に返しています

このとき、呼び出すコンストラクタは必ずしもクラスの型と同じである必要はないのです
何でもよいというわけにはいきません。あくまで型に互換性があるということが条件です
つまり、クラス型のサブクラスであればコンストラクタを呼び出せるのです

スーパークラスとサブクラス間でメソッドのシグネチャが衝突した場合
サブクラスのメソッドはスーパークラスのメソッドを「オーバーライド」します
どのメソッドを実行するかは変数の型ではなくオブジェクトの型にあります
実例を見てみましょう
class super_class {
	void method() {
		System.out.println("kitty on your lap");
	}
}

class sub_class extends super_class {
	void method() {
		System.out.println("Card Captor Sakura");
	}
}

class third_class extends sub_class {
	void method() {
		System.out.println("LOVE HINA");
	}
}

class test {
	public static void main(String args[]) {
		sub_class obj = new sub_class();
		obj.method();
	}
}
このプログラムは、main()メソッドを見れば容易に結果は見えるでしょう
sub_classのmethod()が実行されます
型がsub_class()で、さらにコンストラクタで呼び出されたオブジェクトもsub_class()なら当然です
しかし、このプログラムを以下のように改良しても問題なく実行できます
class super_class {
	String str = "this class is super_class";
	void method() {
		System.out.println("kitty on your lap");
	}
}

class sub_class extends super_class {
	void method() {
		System.out.println("Card Captor Sakura");
	}
}

class third_class extends sub_class {
	String str = "this class is third_class";
	void method() {
		System.out.println("LOVE HINA");
	}
}

class test {
	public static void main(String args[]) {
		super_class obj = new third_class();
		obj.method();
		System.out.println(obj.str);
	}
}
main()メソッドの、コンストラクタ呼び出しに注目してください
変数の型は super_classですが、生成したインスタンスの参照はthird_classです

third_classはsuper_classのサブクラスだから、このプログラムは正常にコンパイルができます
super_classとthird_classには、シグネチャが同じメソッドがあります
このときthird_classのmethod()メソッドはスーパークラスのmethod()をオーバーライドします

しかし、メンバ変数はオーバーライドされません
型がsuper_classなので、メンバ変数はsuper_classのメンバ変数が参照されます
結果は次のようになりました

LOVE HINA
this class is super_class

実行されたメソッドは、third_class()のメソッドです
これは、super_classの変数を用いてthird_classのオブジェクトを参照したためです
オーバーロードはコンパイル時にコンパイラがどのメソッドを実行するべきかを判断してコードが作成されます
しかし、オーバーライドは実行中に適切なメソッドを選択するという、非常に動的なポリモーフィズムです
このことはオーバーロードとオーバーライドの大きな違いなので、非常に重要な部分です
オーバーライドメソッドはどのメソッドが的確なのか、コンパイル時ではなく実行時に決まるのです
適切なオーバーライドメソッド選択するJavaのこの機能を動的メソッドディスバッチと呼びます


クラス型の拡張と縮小

クラス型は、上記したように互換性があれば
必ずしも同一のクラスのインスタンスである必要はありません

オーバーライドを目的としなくても、頻繁にそのような手法を実用します
たとえば、Aというクラス型を受け取るメソッドに対し
Aクラスを拡張しているBクラスのインスタンスを渡すということが可能です
このようなクラスやメソッドの仕様は、Javaのライブラリで頻繁に見られます

スーパークラス型の変数に、サブクラスの参照を代入することは問題ありません
ただし、その逆を行う場合は明示的にキャストする必要があります
class Super {
	String str1 = "Kitty on your lap";
}

class Sub extends Super {
	String str2 = "Card Captor Sakura";
}

class test {
	public static void main(String args[]) {
		Super sp = new Sub();
		Sub sub = (Sub)sp;
	}
}
Superクラス型の変数 sp に Sun()への参照を代入する場合は問題ありません
これは、SuperクラスのメンバにSub()クラスのメンバを拡張するだけですね
もしSuperクラスを利用するメソッドやクラスにSub型を渡しても
Superクラスしか利用しないので、Super型にSubクラスのインスタンスを渡して縮小しても
この場合は問題ありません

しかし、Sub型の変数にSuperクラスの参照を代入する場合は問題があります
Subクラスのメンバを利用するとき、Superのインスタンスにはメンバが存在しない可能性があります
この場合は、キャストすることによって型を明示的に変換する必要があります



前のページへ戻る次のページへ