多態性

みるくあいらんどっ! > ドキュメント > Java > じっくり学ぶ Java講座 [初心者向け・入門]


多態性

以下のようなプログラムについて考えます。

		Dog dog = new WiseDog("ポチ");
		dog.cry();

このプログラムの 1行目のように、Javaでは、参照型変数の型(Dog)とインスタンスの型(WiseDog)が異なる場合がありました。そして、dog.cry();のメソッド呼び出しでは、インスタンスの型(WiseDog)の cryメソッドが呼び出されるのでした。

と言うことは、いろいろなクラスを用意した場合には、dog.cry();の処理内容はさまざまに変わる可能性があります。この性質のことを多態性と言います。本節では、2つのサンプルプログラムで、多態性を見ていきます。

なお、多態性とはポリモルフィズムとも呼ばれます。

多態性の例 (1) Animalクラス

概要

これまで製作してきた Dog系クラス、Cat系クラス、Crow系クラスをすべてまとめて、多態性の動きを確認します。

イメージとしては以下のような感じです。このイメージの中に本来はメソッドも記述すべきですが、省略しています。

多態性 (1)

実装

Animalクラス、Dogクラス、WiseDogクラス、Catクラス、WiseCatクラスは M502プロジェクトのものをそのまま利用、Birdクラス、Crowクラスh M503プロジェクトのものをそのまま利用、HowlingDogクラスは M204プロジェクトのものをそのまま利用しています。

ソースコードは以下の通り。

M601/Animal.java

/**
 * 動物を表す抽象クラスです。
 */
public abstract class Animal {
	/**
	 * 名前。
	 */
	protected String name;
	/**
	 * 鳴く回数。
	 */
	protected int cryCount;
	
	/**
	 * 名前を設定します。
	 * @param name 名前
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * 鳴く回数を設定します。
	 * @param cryCount 鳴く回数
	 */
	public void setCryCount(int cryCount) {
		this.cryCount = cryCount;
	}
	
	/**
	 * 鳴きます。
	 */
	public abstract void cry();
}

M601/Dog.java

/**
 * 犬を表すクラスです。
 */
public class Dog extends Animal {
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public Dog(String name) {
		this(name, 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public Dog(String name, int cryCount) {
		this.setName(name);
		this.setCryCount(cryCount);
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ワン");
		}
		System.out.println("」");
	}
	
	/**
	 * お座りします。
	 */
	public void sitDown() {
		System.out.println(this.name + "は座りました。");
	}
	
	@Override
	public String toString() {
		return "[Dog] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M601/WiseDog.java

/**
 * 賢い犬を表すクラスです。
 */
public class WiseDog extends Dog {
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public WiseDog(String name) {
		super(name);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public WiseDog(String name, int cryCount) {
		super(name, cryCount);
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("キャン");
		}
		System.out.println("」");
	}
	
	/**
	 * お手をします。
	 */
	public void touch() {
		System.out.println(this.name + "はお手をしました。");
	}
	
	@Override
	public String toString() {
		return "[WiseDog] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M601/HowlingDog.java

/**
 * 遠吠えする犬を表すクラスです。
 */
public class HowlingDog extends Dog {
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public HowlingDog(String name) {
		super(name);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public HowlingDog(String name, int cryCount) {
		super(name, cryCount);
	}
	
	@Override
	public void cry() {
		int random = (int)(Math.random() * 2);
		
		if(random == 0) {
			// 既存の鳴き声で鳴く
			super.cry();
		} else {
			// 遠吠えする
			System.out.print(this.name + "「ワオ");
			for(int i = 0; i < this.cryCount; i ++) {
				System.out.print("ー");
			}
			System.out.println("ン」");
		}
	}
}

M601/Cat.java

/**
 * 猫を表すクラスです。
 */
public class Cat extends Animal {
	/**
	 * コンストラクタ。
	 */
	public Cat() {
		this("名無しの猫", 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public Cat(String name) {
		this(name, 1);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public Cat(String name, int cryCount) {
		this.setName(name);
		this.setCryCount(cryCount);
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("ニャー");
		}
		System.out.println("」");
	}
	
	@Override
	public String toString() {
		return "[Cat] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M601/WiseCat.java

/**
 * 賢い猫を表すクラスです。
 */
public class WiseCat extends Cat {
	/**
	 * コンストラクタ。
	 */
	public WiseCat() {
		; // super(); が省略されている
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 */
	public WiseCat(String name) {
		super(name);
	}
	
	/**
	 * コンストラクタ。
	 * @param name 名前
	 * @param cryCount 鳴く回数
	 */
	public WiseCat(String name, int cryCount) {
		super(name, cryCount);
	}
	
	/**
	 * 横になります。
	 */
	public void lie() {
		System.out.println(this.name + "は横になりました。");
	}
	
	@Override
	public String toString() {
		return "[WiseCat] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M601/Bird.java

/**
 * 鳥を表す抽象クラスです。
 */
public abstract class Bird extends Animal {
	
	/**
	 * 飛びます。
	 */
	public void fly() {
		System.out.println(this.name + "は飛びました。");
	}
}

M601/Crow.java

/**
 * 犬を表すクラスです。
 */
public class Crow extends Bird {
	/**
	 * コンストラクタ。
	 */
	public Crow() {
		this(1);
	}
	
	/**
	 * コンストラクタ。
	 * @param cryCount 鳴く回数
	 */
	public Crow(int cryCount) {
		this.setName("名無しのカラス");
		this.setCryCount(cryCount);
	}
	
	@Override
	public void cry() {
		System.out.print(this.name + "「");
		for(int i = 0; i < this.cryCount; i ++) {
			System.out.print("カー");
		}
		System.out.println("」");
	}
	
	@Override
	public String toString() {
		return "[Crow] name:" + this.name + ", cryCount:" + this.cryCount;
	}
}

M601/M601.java

/**
 * 多態性を確認します。
 */
public class M601 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		Animal[] animals = new Animal[] {
				new Dog("ポチ"),
				new WiseDog("ラッキー", 5),
				new HowlingDog("ハチ", 10),
				new Cat(),
				new WiseCat("タマ", 3),
				new Crow(5),
		};
		
		for(int i = 0; i < animals.length; i ++) {
			animals[i].cry();
		}
	}
}

実行結果の例は以下の通り。

ポチ「ワン」
ラッキー「キャンキャンキャンキャンキャン」
ハチ「ワオーーーーーーーーーーン」
名無しの猫「ニャー」
タマ「ニャーニャーニャー」
名無しのカラス「カーカーカーカーカー」

cryメソッドの呼び出しによって、それぞれのインスタンスに応じた処理が行われていますね。これが多態性ということになります。

ただ、このプログラムでは、プログラムの規模が大きく手間がかかった割には全然、利用価値が無いように思えます。そこで、続いて別の例を挙げてみたいと思います。

多態性の例 (2) Questionクラス

概要

抽象クラス Questionとそれを継承したクラスによって、多態性を生かしたクイズゲームを作成します。具体的には以下の 4種類の問題形式を入り混ぜたクイズゲームを製作します。

  • 解答を入力する形式(SimpleQuestion)。
  • 文字数のヒントを出す形式(LengthCheckQuestion)。
  • はい/いいえで解答する形式(YesNoQuestion)。
  • 複数の選択肢から解答する形式(SelectedQuestion)。

イメージとしては以下のような感じです。このイメージの中に本来はメソッドも記述すべきですが、省略しています。

多態性 (2)

実装

ソースコードは以下の通り。

M602/MySystem.java(ライブラリをそのまま利用します)

M602/Question.java

/**
 * 問題を表す抽象クラスです。
 */
public abstract class Question {
	
	/**
	 * 問題文を返します。
	 * @return 問題文
	 */
	public abstract String getQuestion();
	
	/**
	 * 入力が正常かどうかチェックします。
	 * @param input 入力
	 * @return 入力が正常の場合 true
	 */
	public abstract boolean check(String input);
	
	/**
	 * 正解かどうかを返します。
	 * @param input 入力
	 * @return 正解の場合 true
	 */
	public abstract boolean isCorrect(String input);
	
	/**
	 * 解答文を返します。
	 * @return 解答文
	 */
	public abstract String getAnswer();
}

M602/SimpleQuestion.java

/**
 * シンプルな問題を表すクラスです。
 */
public class SimpleQuestion extends Question {
	/**
	 * 問題。
	 */
	private String question;
	/**
	 * 解答。
	 */
	private String answer;
	
	/**
	 * コンストラクタ。
	 * @param question 問題
	 * @param answer 解答
	 */
	public SimpleQuestion(String question, String answer) {
		this.question = question;
		this.answer = answer;
	}
	
	@Override
	public String getQuestion() {
		return this.question;
	}

	@Override
	public boolean check(String input) {
		return true;
	}

	@Override
	public boolean isCorrect(String input) {
		return this.answer.equals(input);
	}

	@Override
	public String getAnswer() {
		return this.answer;
	}
}

M602/LengthCheckQuestion.java

/**
 * 入力文字の長さをチェックする問題を表すクラスです。
 */
public class LengthCheckQuestion extends SimpleQuestion {
	
	/**
	 * コンストラクタ。
	 * @param question 問題
	 * @param answer 解答
	 */
	public LengthCheckQuestion(String question, String answer) {
		super(question, answer);
	}
	
	@Override
	public String getQuestion() {
		return super.getQuestion() + "(正解は " + this.getAnswer().length() + "文字)";
	}
	
	@Override
	public boolean check(String input) {
		return this.getAnswer().length() == input.length();
	}
}

M602/YesNoQuestion.java

/**
 * 「はい」または「いいえ」を問う問題を表すクラスです。
 */
public class YesNoQuestion extends Question {
	/**
	 * 問題。
	 */
	private String question;
	/**
	 * 解答。
	 */
	private boolean answer;
	
	/**
	 * コンストラクタ。
	 * @param question 問題
	 * @param answer 解答
	 */
	public YesNoQuestion(String question, boolean answer) {
		this.question = question;
		this.answer = answer;
	}
	
	@Override
	public String getQuestion() {
		return this.question + "(y/n)";
	}

	@Override
	public boolean check(String input) {
		if(input.equals("y") || input.equals("Y")) {
			return true;
		} else if(input.equals("n") || input.equals("N")) {
			return true;
		} else {
			return false;
		}
	}

	@Override
	public boolean isCorrect(String input) {
		if(input.equals("y") || input.equals("Y")) {
			// 「はい」と答えた場合は、正しい場合に trueを返す
			return this.answer;
		} else if(input.equals("n") || input.equals("N")) {
			// 「いいえ」と答えた場合は、正しくない場合に trueを返す
			return ! this.answer;
		} else {
			// プログラムの流れが正しければ、決してここには到達しない
			// 例外については、「エラーと例外」の章を参照すること
			throw new IllegalArgumentException();
		}
	}

	@Override
	public String getAnswer() {
		return this.answer ? "yes" : "no";
	}
}

M602/SelectedQuestion.java

/**
 * 選択する問題を表します。
 */
public class SelectedQuestion extends Question {
	/**
	 * 問題。
	 */
	private String question;
	/**
	 * 解答群。
	 */
	private String[] answers;
	/**
	 * 解答。
	 */
	private String answer;
	
	/**
	 * コンストラクタ。
	 * @param question 問題
	 * @param answers 解答群(最初の解答を正解に設定すること)
	 */
	public SelectedQuestion(String question, String[] answers) {
		this.question = question;
		this.answer = answers[0];
		
		// 解答群をコピーする
		this.answers = new String[answers.length];
		for(int i = 0; i < answers.length; i ++) {
			this.answers[i] = answers[i];
		}
		
		// 解答群をシャッフルする
		this.shuffleAnswers();
	}
	
	/**
	 * 解答群をシャッフルします。
	 */
	private void shuffleAnswers() {
		for(int i = 0; i < this.answers.length; i ++) {
			int value = (int)(Math.random() * this.answers.length);
			String temp = this.answers[value];
			this.answers[value] = this.answers[i];
			this.answers[i] = temp;
		}
	}
	
	@Override
	public String getQuestion() {
		StringBuilder sb = new StringBuilder();
		sb.append(this.question + "(");
		for(int i = 0; i < this.answers.length; i ++) {
			sb.append(this.answers[i]);
			if(i != this.answers.length - 1) {
				sb.append(",");
			}
		}
		sb.append(")");
		
		return sb.toString();
	}

	@Override
	public boolean check(String input) {
		for(int i = 0; i < this.answers.length; i ++) {
			if(this.answers[i].equals(input)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean isCorrect(String input) {
		return this.answer.equals(input);
	}

	@Override
	public String getAnswer() {
		return this.answer;
	}

}

M602/M602.java

/**
 * クイズゲームです。
 */
public class M602 {
	
	/**
	 * メインメソッド。
	 * @param args 引数
	 */
	public static void main(String[] args) {
		// 問題群を生成する
		Question[] questions = new Question[] {
				new SimpleQuestion("りんごを表す英単語は", "apple"),
				new SimpleQuestion("みかんを表す英単語は", "orange"),
				new LengthCheckQuestion("太陽を表す英単語は", "sun"),
				new LengthCheckQuestion("月を表す英単語は", "moon"),
				new YesNoQuestion("womanの複数形は women", true),
				new YesNoQuestion("forgetの過去形は forgetted", false),
				new SelectedQuestion("火曜日を表す英単語は", new String[] {
						"Tuesday",
						"Monday",
						"Wednesday",
						"Thursday",
						"Friday",
				}),
				new SelectedQuestion("childの複数形は", new String[] {
						"children",
						"child",
						"childs",
						"childes",
				}),
		};
		
		// 正解数
		int score = 0;
		
		// 問題群をシャッフルする
		M602.shuffleQuestions(questions);
		
		System.out.println("------------");
		System.out.println("クイズゲーム");
		System.out.println("------------");
		for(int i = 0; i < questions.length; i ++) {
			Question question = questions[i];
			System.out.println("第 " + (i + 1) + "問:");
			String input;
			
			// ユーザからの入力を受け付ける
			do {
				input = MySystem.in.getString(question.getQuestion());
				input = input.trim();
			} while(! question.check(input));
			
			// 正解かどうか判定をする
			boolean isCorrect = question.isCorrect(input);
			if(isCorrect) {
				System.out.println("正解!");
				score ++;
			} else {
				System.out.println("不正解。正解は " + question.getAnswer() + "でした。");
			}
		}
		
		// ゲームを終了する
		System.out.println("------------");
		System.out.println(questions.length + "問中 " + score + "問正解でした。");
	}
	
	/**
	 * 引数に設定した問題群をシャッフルします。
	 * @param questions 問題群
	 */
	private static void shuffleQuestions(Question[] questions) {
		for(int i = 0; i < questions.length; i ++) {
			int value = (int)(Math.random() * questions.length);
			Question temp = questions[value];
			questions[value] = questions[i];
			questions[i] = temp;
		}
	}
}

実行結果の例は以下の通り。

------------
クイズゲーム
------------
第 1問:
太陽を表す英単語は(正解は 3文字)? moon
太陽を表す英単語は(正解は 3文字)? sun
正解!
第 2問:
womanの複数形は women(y/n)? n
不正解。正解は yesでした。
第 3問:
火曜日を表す英単語は(Wednesday,Tuesday,Thursday,Monday,Friday)? Sunday
火曜日を表す英単語は(Wednesday,Tuesday,Thursday,Monday,Friday)? Tuesday
正解!
第 4問:
月を表す英単語は(正解は 4文字)? star
不正解。正解は moonでした。
第 5問:
forgetの過去形は forgetted(y/n)? y
不正解。正解は noでした。
第 6問:
childの複数形は(childs,childes,child,children)? childs
不正解。正解は childrenでした。
第 7問:
りんごを表す英単語は? apple
正解!
第 8問:
みかんを表す英単語は? orange
正解!
------------
8問中 4問正解でした。

問題形式がばらばらなのに、メインメソッドでは switch文を使用していません。多態性で実現させています。

ソースコードの中に、新しい文法的な要素はほとんどありません。今までの知識でそのほとんどすべてを理解可能です。ただ、簡単なプログラムではありませんので、じっくりと考えて理解すると勉強になると思います。

まずは、自分で問題を追加したり、編集したりすると楽しめると思います。

最終更新: 2013/02/26 , 公開: 2013/02/26
▲top