未来エンジニア養成所Blog

月単価180万以上のプログラミング講師がプログラミングを皆に楽しんでもらうための情報をお届けします。

【Java】オブジェクト指向の基本問題2-14

title


問題2-14

次の3つのインタフェースがあります。
(変更は禁止です)

  • 飛行可能を表現するFlyableインタフェース
    (飛ぶfly抽象メソッドが定義されています)

  • 水泳可能を表現するSwimableインタフェース
    (泳ぐswim抽象メソッドが定義されています)

  • 食事可能を表現するEatableインタフェース
    (食べるeat抽象メソッドが定義されています)

また、食事可能インタフェースを実現した抽象クラスBirdが定義されています。
(変更は禁止です)

ここでは、eatメソッドを実装していません。

なぜなら、鳥の種類によって食べるものが異なるからです。


次の指示にしたがって具体的な鳥を表す3種類のクラスを作成してください。


【つばめSwallowクラス】

  1. Bird抽象クラスを継承します。

  2. Flyableインタフェースを実装します。

  3. 飛ぶメソッドと食べるメソッドを実装します。
    出力内容は下記実行結果を参考にしてください。


【ペンギンPenguinクラス】

  1. Bird抽象クラスを継承します。

  2. Swimableインタフェースを実装します。

  3. 泳ぐメソッドと食べるメソッドを実装します。
    出力内容は下記実行結果を参考にしてください。


【カワセミAtthisクラス】

  1. Bird抽象クラスを継承します。

  2. FlyableとSwimableインタフェースを実装します。

  3. 飛ぶメソッドと泳ぐメソッドと食べるメソッドを実装します。
    出力内容は下記実行結果を参考にしてください。


また、InterfaceBasicPracticeクラスのmainメソッドは一部未完成です。

次のように処理を実装してください。

  1. 鳥配列オブジェクトを拡張for文でループします。

  2. 食べるメソッドを実行します。

  3. もし飛行可能の鳥ならば飛ぶメソッドを実行します。

  4. もし水泳可能の鳥ならば泳ぐメソッドを実行します。


【実行結果】

つばめが虫を食べました。
つばめがスイスイ飛んでいます。

ペンギンが魚を食べました。
ペンギンがスイスイ泳いでいます。

カワセミが小魚を食べました。
カワセミがスイスイ飛んでいます。
カワセミがスイスイ泳いでいます。


【InterfaceBasicPractice.java】

public class InterfaceBasicPractice {
    public static void main(String[] args) {

        // 鳥配列オブジェクトの生成
        Bird[] birds = {new Swallow(), new Penguin(), new Atthis()};


        // 鳥たちの紹介
        // ここからコーディングしてください


    }
}

// 飛行可能インタフェース
interface Flyable {

    // 飛ぶ抽象メソッド
    void fly();

}

// 水泳可能インタフェース
interface Swimable {

    // 泳ぐ抽象メソッド
    void swim();

}

// 食事可能インタフェース
interface Eatable {

    // 食べる抽象メソッド
    void eat();

}

// 鳥抽象クラス
abstract class Bird implements Eatable {}


// ここにつばめクラスを作成してください


// ここにペンギンクラスを作成してください


// ここにカワセミクラスを作成してください


解答例

【InterfaceBasicPractice.java】

public class InterfaceBasicPractice {
    public static void main(String[] args) {

        // 鳥配列オブジェクトの生成
        Bird[] birds = {new Swallow(), new Penguin(), new Atthis()};

        // 鳥たちの紹介
        for(Bird bird : birds) {

            // 食べるメソッド実行
            bird.eat();

            // もし飛行可能なら飛ぶメソッドを実行
            if(bird instanceof Flyable) {
                ((Flyable)bird).fly();
            }

            // もし水泳可能なら泳ぐメソッドを実行
            if(bird instanceof Swimable) {
                ((Swimable)bird).swim();
            }

            System.out.println();
        }

    }
}

// 飛行可能インタフェース
interface Flyable {

    // 飛ぶ抽象メソッド
    void fly();

}

// 水泳可能インタフェース
interface Swimable {

    // 泳ぐ抽象メソッド
    void swim();

}

// 食事可能インタフェース
interface Eatable {

    // 食べる抽象メソッド
    void eat();

}

// 鳥抽象クラス
abstract class Bird implements Eatable {}

// つばめクラス
class Swallow extends Bird implements Flyable {
    public void eat() {
        System.out.println("つばめが虫を食べました。");
    }

    public void fly() {
        System.out.println("つばめがスイスイ飛んでます。");
    }
}

// ペンギンクラス
class Penguin extends Bird implements Swimable {
    public void eat() {
        System.out.println("ペンギンが魚を食べました。");
    }

    public void swim() {
        System.out.println("ペンギンがスイスイ泳いでいます。");
    }
}

// カワセミクラス
class Atthis extends Bird implements Flyable, Swimable {
    public void eat() {
        System.out.println("カワセミが子魚を食べました。");
    }

    public void fly() {
        System.out.println("カワセミがスイスイ飛んでます。");
    }

    public void swim() {
        System.out.println("カワセミがスイスイ泳いでいます。");
    }
}


解説

Javaではプログラミングをするとは、簡単に言うと「クラスという塊を作っていくこと」です。

まずクラスありきなんですが、実はクラスは4人兄弟の長男で3人の弟がいます。

それは「インタフェース」 「列挙型」 「アノテーション」です。



「インタフェース」は特殊なクラスのようなものです。

「クラス」は「インタフェース」を「実装」します。

その時に使用するキーワードが「implements」です。

クラスはインタフェースをカンマ区切りで複数実装できるのもポイントです。



「インタフェース」には大きく2つの目的があります。

1つ目が定数置き場、2つ目が抽象メソッド置き場です。

(注:Java8からインタフェースの仕様が変わり、デフォルトメソッドとクラスメソッドが定義可能になりました。ここではインタフェースの普遍的な説明を行います。)


主な目的は、「抽象メソッド置き場」です。

抽象クラスも抽象メソッドを定義できますが、抽象クラスはそれ以外普通のクラスと同じくフィールドやコンストラクタ、具象メソッドも定義できます。

インタフェースはもっと特化しており、基本的には「抽象メソッドを定義する専門のもの」です。



インタフェースはnewキーワードを使ってインスタンス化することはできませんし、根本的にコンストラクタを持ちません。



クラスにインタフェースを実装することで、そのクラスは抽象メソッドの実装を義務付けられます。


今回、鳥というスーパークラスを定義しました。

食べない鳥はいないので、食事可能を表現するEatableインタフェースを実装していますが、抽象メソッドeatは実装していません。

何を食べるかは鳥によりけりだからです。


なので、鳥クラスは抽象クラスになっています。

次のようにコーディングした人もいると思いますが、あまり良くないプログラムです。

// つばめクラス(あまり良くない!)
class Swallow extends Bird implements Eatable, Flyable {
    ....
}


BirdクラスがもうEatableインタフェースを実装しているので、つばめSwallowクラスでまたEatableインタフェースを実装する必要はありません。



mainメソッドでは鳥配列オブジェクトを生成し、拡張for文を使ってループさせます。

// 鳥たちの紹介
for(Bird bird : birds) {
    ....
}


拡張for文はコロンを使って二つの領域を作り、その右側には配列やコレクションなどの参照変数を置きます。

そしてその左側に変数の宣言を記述することで、ひとつずつ値が渡されてループを繰り返すわけです。


拡張for文の中では、まず食べるメソッドを呼び出すわけですが、これは簡単です。

Bird型はEatableインタフェースを実装しているため、必ずeatメソッドを持っているのでただ呼び出すだけです。



その後に、飛ぶメソッドや泳ぐメソッドを呼び出すわけですが、それは鳥の種類によってまちまちなのできちんとキャスト可能かどうか調べてからメソッド呼び出しを行います。

次のように記述した人はいるでしょうか。

if(bird instanceof Swallow) {
    ((Swallow)bird).fly();
}
if(bird instanceof Penguin) {
    ((Penguin)bird).swim();
}


今回はこれでも正しく実行できますが、今後飛べる鳥や泳げる鳥を増やすという拡張性を考慮すると、FlyableインタフェースやSwimableインタフェースでキャストする方がスマートと言えます。



次のようにコーディングする人も見かけます。

if(bird instanceof Flyable) {
    ((Flyable)bird).fly();
} else if(bird instanceof Swimable) {
    ((Swimable)bird).swim();
}


気持ちは分かりますが、これは致命的なバグです。

if - else if文を使ってしまうと、どちらかしか実行できない排他的な関係になりますが、「飛行可能」「水泳可能」は排他的な関係ではありません。


今回の問題では、カワセミは「飛行可能」かつ「水泳可能」な存在なのでうまく動作しません。



インタフェースのメリットは最初はなかなか分かりにくいものです。

しかし理解が深まると、良好なオブジェクト指向設計が可能になります。


参考図書



LINE公式アカウント

仕事が辛くてたまらない人生が、仕事が楽しくてたまらない人生に変わります。
【登録いただいた人全員に、無料キャリア相談プレゼント中!】


LineOfficial

友だち追加