問題2-11
動物を表すAnimal抽象クラスはすでに完成しています。
(変更は禁止です)
その中に、動物が鳴くことを表現している歌うsingメソッドがあります。
しかし、歌うsingメソッドは抽象メソッドであり、中身は実装していません。
なぜなら、動物によって鳴き声が違うから実装できないのです。
具体的な動物として、次の3つのクラスを作成してください。
- 犬クラス Dog
- 猫クラス Cat
- 鳥クラス Bird
下記の実行結果を参考にして、歌うsingメソッドを実装してください。
また、AbstractBasicPracticeクラスのmainメソッドは未完成です。
以下の手順にしたがってmainメソッドを実装してください。
3匹の動物たちを表すAnimal配列型変数animalsの宣言と配列オブジェクトの生成を行います。
動物たち配列変数の各要素に、順番に犬・猫・鳥オブジェクトを生成・代入します。
forループを使って動物たち配列変数を順にみていき、各動物の歌うsingメソッドを呼び出します。
【実行結果】
わんわん にゃーにゃー ぴよぴよ
【AbstractBasicPractice.java】
public class AbstractBasicPractice { public static void main(String[] args) { // 3匹の動物たちを表すAnimal配列型変数animalsの宣言と // 配列オブジェクトの生成 // 動物たち配列変数の各要素に、順番に犬・猫・鳥 // オブジェクトを生成・代入 // forループを使って動物たち配列変数を順にみていき、 // 各動物の歌うsingメソッドの呼び出し } } // 動物クラス abstract class Animal { public abstract void sing(); } // ここにDogクラスを作成してください // ここにCatクラスを作成してください // ここにBirdクラスを作成してください
解答例
【AbstractBasicPractice.java】
public class AbstractBasicPractice { public static void main(String[] args) { // 3匹の動物たちを表すAnimal配列型変数animalsの宣言と // 配列オブジェクトの生成 Animal[] animals = new Animal[3]; // 動物たち配列変数の各要素に、順番に犬・猫・鳥 // オブジェクトを生成・代入 animals[0] = new Dog(); animals[1] = new Cat(); animals[2] = new Bird(); // forループを使って動物たち配列変数を順にみていき、 // 各動物の歌うsingメソッドの呼び出し for(int i = 0; i < animals.length; i++) { animals[i].sing(); } } } // 動物クラス abstract class Animal { public abstract void sing(); } // 犬クラス class Dog extends Animal { public void sing() { System.out.println("わんわん"); } } // 猫クラス class Cat extends Animal { public void sing() { System.out.println("にゃーにゃー"); } } // 鳥クラス class Bird extends Animal { public void sing() { System.out.println("ぴよぴよ"); } }
解説
「抽象クラス」と「抽象メソッド」は、最初はつかみどころのない考え方に思えますが、非常に重要です。
とは言え、あまり難しく考えないようにしましょう。
まずは「抽象クラス」ですが、一番のポイントは「newキーワードを用いてインスタンス化できない」という点です。
その理由は、こう考えると簡単です。
たとえば小さな子どもがあなたのところにきてこう言いました。
「ねぇ、犬を描いてよ。」
あなたは紙にペンで犬の絵を描きました。
次に、その子どもはこう言いました。
「次は、動物を描いてよ。」
さて、あなたはどうしますか?
私ならこう子どもに質問します。
「なんの動物を描けば良いかな?」
犬の絵を描くことができる、つまりDogクラスはnewキーワードでインスタンス化できます。
でも、動物そのものの絵を描くことはできません。
なぜなら、「動物」とは「犬」や「猫」を総称している「抽象的な概念」だからです。
だから、今回の問題もAnimalクラスは抽象クラスなわけです。
では、抽象クラスは何のためにあるのでしょうか。
簡単に言うと「スーパークラスになるべくして生まれたクラス」と言えます。
抽象クラスは「抽象メソッド」を持つことができます。
「抽象メソッド」とは、中身のないメソッドです。
「抽象クラス」と「抽象メソッド」の関係にもポイントがあります。
「抽象メソッドを持つクラスは必ず抽象クラス」ですが、「抽象クラスが必ずしも抽象メソッドを持つとは限らない」点を押さえておいてください。
中身のないメソッド「抽象メソッド」には何の意味があるのでしょうか。
それは「サブクラスで中身を実装しますので今は保留にさせてください」ということです。
抽象クラスは一般的に抽象メソッドを持ち、サブクラスで抽象メソッドの中身を記述(実装と言います)すると、そのクラスは抽象ではない普通のクラス(具象クラスと言います)になれます。
そうすると、newを使ってインスタンス化できるわけです。
ちなみに「抽象メソッドの実装」は「オーバーライド」に近いものですが、普通は「@Override」アノテーションを付けません。
それでは、問題の解説に戻ります。
今回はAnimal抽象クラスがあり、その中に動物が鳴くことを表現するsingという抽象メソッドがあります。
「動物の鳴き声」というのは、動物の種類が分からないと特定できません。
ですので、singメソッドは抽象メソッドであり、Animalクラスはサブクラス、つまり具体的な動物の種類を表現するクラスがあることが前提のクラスなんです。
たとえば、犬Dogクラスはこのように実装しました。
// 動物抽象クラス abstract class Animal { public abstract void sing(); } // 犬クラス class Dog extends Animal { public void sing() { System.out.println("わんわん"); } }
抽象クラスや抽象メソッドにはキーワード「abstract」が付きます。
抽象クラスはサブクラスに抽象メソッドの実装を義務付けるわけです。
そしてサブクラスで抽象メソッドを実装すると、そのクラスは晴れて「abstract」の付かない具象クラスとなるわけです。
次のコードでも良いのではないかと考えた人はいますか?
// 動物抽象クラス abstract class Animal { } // 犬クラス class Dog extends Animal { public void sing() { System.out.println("わんわん"); } }
しかし、これだとコンパイルが通りません。
何故でしょうか。
ここから、さらに大切なお話をします。
「継承」の本当のメリットのお話しです。
「差分コーディング」は継承のメリットとしてほんのわずかなものです。
本当のメリットは「グループ化」にあります。
Javaの参照型には、2つの型の考え方があります。
オブジェクトそのものが持つ「実際の型」と、そのオブジェクトを指す参照変数が持つ「扱いとしての型」です。
たとえば、Dog d = new Dog();
というプログラムがあります。
new Dog()
で生まれたオブジェクトの型はまさに「Dog型」です。
そして参照変数 d
は「Dog型オブジェクト」を「Dog」として扱っているわけです。
「犬を犬として扱う」、とても普通ですね。
継承すると何が嬉しいかというと、DogクラスにスーパークラスAnimalがあれば、Animal a = new Dog();
といったプログラムを記述できる点です。
つまり、参照変数の型は実際のオブジェクトの型と同じか、そのスーパークラスでも良いのです。
今回の例で言えば「犬を動物として扱う」ということです。
Animal a = new Dog();
の後にa.sing();
と記述したとしましょう。
これは、Animalクラス(あるいはそれよりスーパークラス)にsingメソッドが定義してあればコンパイルが通ります。
コンパイラは参照変数 a
をあくまでAnimal型として捉えるためです。
ですので、Dogクラスにだけsingメソッドがあってもコンパイルが通らないのです。
そしてここがポイントですが、Animalクラス(あるいはそれよりスーパークラス)にsingメソッドが定義してあれば良い、それは抽象メソッドであっても良いということです。
抽象メソッドはいずれサブクラスで実装しますという約束をコンパイラにしていることになるからです。
参考図書
LINE公式アカウント
仕事が辛くてたまらない人生が、仕事が楽しくてたまらない人生に変わります。
【登録いただいた人全員に、無料キャリア相談プレゼント中!】