インターフェイスは場面によって利用法が大きく異なり、大別すると以下の 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することができるようになりました。