【Java】メソッドのオーバーライド、ポリモーフィズム

2018年3月11日

本稿は継承したメソッドをオーバーライドする方法と、それによっておこるポリモーフィズムという特性について説明します。

メソッドのオーバーライド

オーバーライドしたい親クラスのメソッド名と、引数のパターンも同じにすることで、そのメソッドをオーバーライドできます。

また、メソッドの上に@Overrideと記述することで、オーバーライドしていることを明示することもできます。

@Overrideをつけておくと、オーバーライドできていない場合にコンパイルエラーが発生します。

それではオーバーライドのサンプルをご覧ください。

public class Test{
	public static void main(String[] args){
		Neko neko = new Neko();
		neko.run();
	}
}

class Chara{
	void run(){
		System.out.println("走りました。");
	}
}

class Neko extends Chara{
	@Override
	void run(){
		System.out.println("猫は走りました。");
	}
}
実行結果

猫は走りました。

メソッドのオーバーライドとポリモーフィズム

現時点でオーバーライドしたところでメリットが感じられないかもしれませんが、オーバーライドをすることによって起こる、ポリモーフィズムという特性がそれなりのメリットをもたらします。

ポリモーフィズムとは、継承したクラスのオブジェクトがそれぞれ違った振る舞いをすることです。

それでは例を見てみましょう。

public class Test{
	public static void main(String[] args){
		Animal animal1 = new Cat();
		animal1.run();
		
		Animal animal2 = new Dog();
		animal2.run();
	}

}
class Animal{
	void run(){
		System.out.println("走った");
	}
}
class Cat extends Animal{
	void run(){
		System.out.println("猫は走った");
	}
}
class Dog extends Animal{
	void run(){
		System.out.println("犬は走った");
	}
}
実行結果

猫は走った
犬は走った

ここで重要なポイントは

Animal animal1 = new Cat();

Animal animal2 = new Dog();

です。

なぜかAnimal型の変数に代入できているわけですが、実は継承元の変数型には代入できるという特徴があります。

次に、Animal型の変数に代入してrunメソッドを呼び出しているのに、呼び出し先がそれぞれCatクラス、Dogクラスのrunメソッドが呼び出されています。

この特性こそがポリモーフィズムなのです。

だから何?って感じかもしれませんが、継承元のクラス型の配列などで、一括処理ができたりするのでめちゃくちゃ便利なのです。

実際にポリモーフィズムを利用した、少し複雑なプログラムを見てみましょう。

public class Test{
	public static void main(String[] args){
		MagicalGirl[] girls = getAgreementGirls();
		//それぞれの魔法少女の行動を5回見てみます。
		for ( int i = 0;i < 5;i++ ) {
			System.out.println("===   "+(i+1)+"回目開始   ===");
			for ( int j = 0;j < girls.length;j++ ) {
				System.out.println(girls[j].name+"の行動");
				girls[j].action();
				System.out.println();
			}
			System.out.println("===   "+(i+1)+"回目終了   ===\n");
		}
	}
	static MagicalGirl[] getAgreementGirls(){
		return new MagicalGirl[]{
			new PinkGirl()
			,new YellowGirl()
			,new BlackGirl()
		};
	}

}
class MagicalGirl{
	String name;
	int hp=100;
	int mp=100;
	void action(){}
}
class PinkGirl extends MagicalGirl{
	PinkGirl(){
		super.name="まど〇";
	}
	void action(){
		System.out.println(" こんなの絶対おかしいよ!!");
	}
}
class YellowGirl extends MagicalGirl{
	YellowGirl(){
		super.name="マ〇";
	}
	void action(){
		if ( hp <= 0 ) {
			System.out.println(" 既にマ〇られ済");
		} else {
			if ( mp >= 40 ) {
				System.out.println(" ティロ・フィナー〇発動");
				mp-=40;
			} else {
				//MPがもうないから魔法が使えない
				System.out.println(" ティロ・フィナー〇はもう使えない!!");
				System.out.println(" マ〇は50のダメージを受けた。");
				hp -= 50;
			}
		}
	}
}
class BlackGirl extends MagicalGirl{
	int bp=100;
	BlackGirl(){
		super.name = "ほむ〇";
	}
	void action(){
		if ( bp <= 0 ) {
			System.out.println(" 銃弾がない!!");
		} else {
			System.out.println(" 銃を50発発射");
			bp-=50;
		}
		if ( bp <= 0 ) {
			//弾がなかったら時間停止して補充しに行きます。
			if ( mp >= 100 ) {
				System.out.println(" 時間停止魔法を発動");
				System.out.println(" 補充完了");
				bp+=100;
				mp-=100;
			} else {
				//MPがもうないから魔法が使えない
				System.out.println(" 時間停止魔法はもう使えない!!");
			}
		}
	}
}
実行結果

=== 1回目開始 ===
まど〇の行動
 こんなの絶対おかしいよ!!

マ〇の行動
 ティロ・フィナー〇発動

ほむ〇の行動
 銃を50発発射

=== 1回目終了 ===

=== 2回目開始 ===
まど〇の行動
 こんなの絶対おかしいよ!!

マ〇の行動
 ティロ・フィナー〇発動

ほむ〇の行動
 銃を50発発射
 時間停止魔法を発動
 補充完了

=== 2回目終了 ===

=== 3回目開始 ===
まど〇の行動
 こんなの絶対おかしいよ!!

マ〇の行動
 ティロ・フィナー〇はもう使えない!!
 マ〇は50のダメージを受けた。

ほむ〇の行動
 銃を50発発射

=== 3回目終了 ===

=== 4回目開始 ===
まど〇の行動
 こんなの絶対おかしいよ!!

マ〇の行動
 ティロ・フィナー〇はもう使えない!!
 マ〇は50のダメージを受けた。

ほむ〇の行動
 銃を50発発射
 時間停止魔法はもう使えない!!

=== 4回目終了 ===

=== 5回目開始 ===
まど〇の行動
 こんなの絶対おかしいよ!!

マ〇の行動
 既にマ〇られ済

ほむ〇の行動
 銃弾がない!!
 時間停止魔法はもう使えない!!

=== 5回目終了 ===

この例では、3体の独自行動するキャラを同じ配列で管理しています。

そして、actionメソッドを同じタイミングで、同じ回数呼び出します。

結果をみれば、それぞれのキャラが独自の行動をしているのがわかると思います。

実際にシューティングゲームやアクションゲームではこのポリモーフィズムを利用した配列等でオブジェクトを管理しておき、敵キャラとプレイヤーキャラの配列をループで回し、それぞれ当たり判定していくといった使い方ができます。


余談かもしれませんが、Javaの全てのクラス(参照型)はObjectクラスを継承しています。

自作したクラスも暗黙的に extends Objectが付けられます。

そして、配列型もObject型を継承しています。

つまり、全ての参照型変数はObject型に代入可能です。

ただし、プログラムがわかりにくくなるので、理由がない限りはやらないほうがいいと思います。

Java

Posted by nompor