インターフェイス (3)

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


名詞系インターフェイス

インターフェイスは場面によって利用法が大きく異なり、大別すると以下の 3通りに分けられます。

  1. ~able系…「~が可能である」ことを表すインターフェイス。
  2. ~Listener系…イベントの通知を行うためのインターフェイス。
  3. 名詞系…抽象クラスのようなインターフェイス。

先の章で 1つめと 2つめについて解説しました。本節では 3つめについて解説していきます。

抽象クラスとインターフェイス

抽象クラスとインターフェイスは、abstractなメソッドがあるという点で、似通っています。特に、「フィールドがない(あるいは finalなフィールドしかない)、かつ、抽象メソッドしかない(abstractではないメソッドがない)」抽象クラスの場合には、その抽象クラスとインターフェイスはさらに類似性を持つことになります。

Javaでは多重継承が禁止されており、予約語 extendsにはひとつのクラスしか指定することができません。しかし、インターフェイスは複数を実装することが可能です。このような事情から、抽象クラスのようなインターフェイスを用意して、インターフェイスとして実装することがあります。

先の章の話になりますが、ArrayListクラスの仕様書を見てみましょう。Java SE 7(Java 1.7)の説明書(API)を掲示しておきます→ArrayListクラス。ここには以下のように記述されています。

この中で、以下の記述があります。

java.lang.Object
   java.util.AbstractCollection<E>
      java.util.AbstractList<E>
          java.util.ArrayList<E>

すべての実装されたインタフェース:
    Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess

ArrayList<E>の「<E>」については後で学びます。ArrayListクラスは、AbstractListを継承していることが分かります。一方で、Collectionインターフェイスと Listインターフェイスという名詞形インターフェイスを実装しています。つまり、ArrayListは、AbstractListとみなすこともできれば、Collectionとみなすこともできる、また Listとみなすこともできるということになります。

Collectionや Listがインターフェイスではなく抽象クラスであったなら、多重継承になってしまいます。名詞形インターフェイスを利用することで、このあたりの問題を上手く回避しています。

抽象クラスのようにインターフェイスを利用する

「第13章: クラスの拡張」の「多態性」で作成したクイズゲームで使用した抽象クラスは、abstractなメソッドしか存在しないため、インターフェイスに変更することができます。これを、インターフェイスを使用したものに改修してみます。

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

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

R201/Question.java

/**
 * 問題を表すインターフェイスです。
 */
public interface 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();
}

R201/SimpleQuestion.java

/**
 * シンプルな問題を表すクラスです。
 */
public class SimpleQuestion implements 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;
	}
}

R201/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();
	}
}

R201/YesNoQuestion.java

/**
 * 「はい」または「いいえ」を問う問題を表すクラスです。
 */
public class YesNoQuestion implements 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";
	}
}

R201/SelectedQuestion.java

/**
 * 選択する問題を表します。
 */
public class SelectedQuestion implements 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;
	}

}

R201/R201.java

/**
 * クイズゲームです。
 */
public class R201 {
	
	/**
	 * メインメソッド。
	 * @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;
		
		// 問題群をシャッフルする
		R201.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問正解でした。

LengthCheckQuestion以外は implementsを使用することになりました。これによって別のクラスを extendsすることができるようになりました。

最終更新: 2014/08/01 , 公開: 2014/01/29
▲top