未来エンジニア養成所Blog

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

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

title


問題2-12

AbstractNormalPracticeクラスのmainメソッドは完成しています。
(変更は禁止です)

サッカー選手を表現するSoccerPlayer抽象クラスおよびセンターフォワードを表現するCenterForwardクラスと、ゴールキーパーを表現するGoalKeeperクラスを作成してください。


【SoccerPlayer抽象クラス】

  1. 選手名を表すnameインスタンスフィールド(String型)と背番号を表すuniformNumberインスタンスフィールド(int型)を定義します。
    アクセス指定はprivateに設定します。

  2. 引数に選手名と背番号を受け取るコンストラクタを定義します。
    引数の値をそのままインスタンスフィールドに受け渡します。

  3. 名前を外部から取得できるようにgetNameメソッド(引数なし、戻り値String型)を定義します。

  4. ボールを蹴るkickBallメソッドを定義します。
    「○○はボールを蹴りました」と出力します。

  5. ボールを受け止めるcatchBallメソッドを定義します。
    「○○はボールを足で受け止めました」と出力します。

  6. ポジション名取得抽象メソッドgetPositionNameを宣言します。
    引数はなしで、戻り値はString型です。

  7. toStringメソッドをオーバーライドします。
    「ポジション名 名前 背番号○○」の順に文字列を組み立てます。
    (下記の実行結果を参考にしてください)


【CenterForwardクラス】

  1. SoccerPlayerクラスを継承します。

  2. ポジション名を表現すString型の定数POSITION_NAMEを定義します。
    値は「センターフォワード」とします。

  3. 引数に選手名と背番号を受け取るコンストラクタを定義します。
    SoccerPlayerクラスのコンストラクタをうまく使ってインスタンスフィールドに値を受け渡します。

  4. ポジション名取得メソッドgetPositionNameを実装します。
    戻り値としてポジション名定数の値を返します。


【GoalKeeperクラス】

  1. CenterForwardクラスの手順1〜4と同じです。
    ポジション名定数の値には「ゴールキーパー」を設定してください。

  2. ボールを受け止めるcatchBallメソッドをオーバーライドします。
    「○○はボールを手で受け止めました」と出力します。


【実行結果】

センターフォワード 田村 背番号11
田村はボールを蹴りました
田村はボールを足で受け止めました

ゴールキーパー 川島 背番号12
川島はボールを蹴りました
川島はボールを手で受け止めました


【AbstractNormalPractice.java】

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

        // センターフォワードオブジェクトの生成
        SoccerPlayer fwd = new CenterForward("田村", 11);

        // 自己紹介
        System.out.println(fwd);

        // ボールを処理
        fwd.kickBall();
        fwd.catchBall();

        System.out.println();

        // ゴールキーパーオブジェクトの生成
        SoccerPlayer keeper = new GoalKeeper("川島", 12);

        // 自己紹介
        System.out.println(keeper);

        // ボールを処理
        keeper.kickBall();
        keeper.catchBall();

    }
}


// ここにサッカー選手抽象クラスを作成してください


// ここにセンターフォワードクラスを作成してください


// ここにゴールキーパークラスを作成してください


解答例

【AbstractNormalPractice.java】

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

        // センターフォワードオブジェクトの生成
        SoccerPlayer fwd = new CenterForward("田村", 11);

        // 自己紹介
        System.out.println(fwd);

        // ボールを処理
        fwd.kickBall();
        fwd.catchBall();

        System.out.println();

        // ゴールキーパーオブジェクトの生成
        SoccerPlayer keeper = new GoalKeeper("川島", 12);

        // 自己紹介
        System.out.println(keeper);

        // ボールを処理
        keeper.kickBall();
        keeper.catchBall();

    }
}

// サッカー選手抽象クラス
abstract class SoccerPlayer {

    // 選手名
    private String name;

    // 背番号
    private int uniformNumber;

    // コンストラクタ
    public SoccerPlayer(String name, int uniformNumber) {
        this.name = name;
        this.uniformNumber = uniformNumber;
    }

    // 名前取得メソッド
    public String getName() {
        return name;
    }

    // ボールを蹴るメソッド
    public void kickBall() {
        System.out.println(name + "はボールを蹴りました");
    }

    // ボールを受け止めるメソッド
    public void catchBall() {
        System.out.println(name + "はボールを足で受け止めました");
    }

    // ポジション名取得中傷メソッド
    public abstract String getPositionName();

    @Override
    public String toString() {
        return getPositionName() + " " + name + " 背番号" + uniformNumber;
    }
}

// センターフォワードクラス
class CenterForward extends SoccerPlayer {
    public static final String POSITION_NAME = "センターフォワード";

    // コンストラクタ
    public CenterForward(String name, int uniformNumber) {
        super(name, uniformNumber);
    }

    // ポジション名取得メソッド(実装)
    public String getPositionName() {
        return POSITION_NAME;
    }
}

// ここにゴールキーパークラスを作成してください
class GoalKeeper extends SoccerPlayer {
    public static final String POSITION_NAME = "ゴールキーパー";

    // コンストラクタ
    public GoalKeeper(String name, int uniformNumber) {
        super(name, uniformNumber);
    }

    // ポジション名取得メソッド(実装)
    public String getPositionName() {
        return POSITION_NAME;
    }

    // ボールを受け止めるメソッド(オーバーライド)
    @Override
    public void catchBall() {
        System.out.println(getName() + "はボールを手で受け止めました");
    }
}


解説

「抽象クラス」「抽象メソッド」を持つことができるわけですが、それ以外は基本的に普通のクラス(具象クラス)と同じです。

フィールドやコンストラクタ、普通のメソッドなども定義できます。



一般的なサッカー選手を表現するSoccerPlayerクラスを、今回は抽象クラスとして作成しました。

SoccerPlayerクラスはポジション名取得抽象メソッドgetPositionNameを持ちます。

サッカー選手には必ずポジションがありますが、ポジションはサブクラスとして定義するため抽象メソッドになっているわけです。



SoccerPlayerクラスでは、toStringメソッドをオーバーライドするように指示がありました。

ここで疑問を持った人もいるかもしれません。


「オーバーライドってことは、スーパークラスのメソッドを再定義するんだよね。

でも、SoccerPlayerクラスにはスーパークラスなんてないじゃん!」


クラスを定義するときに、キーワード「extends」を使えばスーパークラスを指定することが可能です。

でも、キーワード「extends」を使わないでクラスを定義すると、自動的に「Object」クラスをスーパークラスとして継承する仕組みになっています。


Javaでは、「Object」クラスはすべてのクラスの原点となるスーパークラスであり、「神様」のような存在です。


「Object」クラスにはいくつかのメソッドがありますが、toStringメソッドはサブクラスでオーバーライドされる前提で定義されたメソッドです。

「toString」、つまり「文字列として」という意味です。


あらゆるオブジェクトは「toString」メソッドを持っていますが、それを呼び出すとそのオブジェクトの特徴などを含めた大切な情報を「文字列として」返します。

ただし、Objectクラスが持っている本来のtoStringメソッドはそれほど良い情報を返しません。


クラスには色々な分類がありますが、データを表現するクラス(値クラスと言います)であれば普通はtoStringメソッドをオーバーライドして有益な情報を文字列として返すようにカスタマイズします。


ぜひ覚えておきましょう。



今回の問題のtoStringメソッドのオーバーライドをよく見てください。

@override
public String toString() {
    return getPositionName() + " " + name + " 背番号" + uniformNumber;
}


文字列を作成するところで、抽象メソッド「getPositionName」を呼び出しています。

ここがポイントです。


なぜ中身のない抽象メソッドを呼び出せるのか、不思議に感じる人もいるかと思います。


答えは「toStringメソッドはインスタンスメソッド」という点にあります。


toStringメソッドを呼んでいるということは、インスタンス化できているということです。

つまり、抽象クラスのサブクラスでちゃんと抽象メソッドを実装しているからそのサブクラスはインスタンス化できるわけです。


オーバーライドも抽象メソッドの実装も同じですが、サブクラス側で再定義・実装したほうが優先的に呼び出されます。

ここが継承や抽象の要と言えるでしょう。



センターフォワードクラスやゴールキーパークラスでは、ポジション名を格納するための「定数」を定義するように指示がありました。

保持する値が変動する「変数」に対して、保持する値が固定の「定数」という考え方がプログラミング言語にはあります。


Javaには、定数宣言用のキーワードは存在しませんが、一般的に「public static final」なフィールドとして定義します。
(状況によりprivateでも構いません)



サッカー選手はポジションに関係なく、普通は足を使ってボールを受け止めます。

ですから、catchBallメソッドはSoccerPlayerクラスに定義しています。


しかし、ゴールキーパーは唯一手を使っても良いポジションなので、catchBallメソッドをオーバーライドして手を使ってボールを受け止めました。


理にかなっていますよね。


参考図書



LINE公式アカウント

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


LineOfficial

友だち追加