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