未来エンジニア養成所Blog

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

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

title


問題2-18

仕事のトラブルを表現するトラブル例外クラスTroubleExceptionはすでに完成しています。
また、社員を表現する社員抽象クラスEmployeeもすでに完成しています。
(変更は禁止です)

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

次の指示に従って、上司を表現するBossクラスと、部下を表現するSubordinateクラスを作成してください。


【Bossクラス】

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

  2. 直属の部下を表すSubordinate型のフィールドを定義します。

  3. 社員名(String型)と部下オブジェクト(Subordinate型)を引数として受け取るコンストラクタを定義します。
    スーパークラスEmployeeのnameフォールドに社員名を設定してください。
    また、Bossクラスのインスタンスフィールドsubに引数の部下オブジェクトを設定してください。

  4. 働くworkメソッドを実装してください。
    ただし、「throws TroubleException」の記述はいりません。
    (上司はトラブルの責任を自分で取ることができるからです)


実行内容は「さて、今回の○○は部下の△△にまかせよう!」と出力し、部下オブジェクトのworkメソッドを実行します。
ただし、例外処理が必須なので注意しましょう。

部下がトラブルを起こした場合はcatchブロックで謝罪します。
内容は「申し訳ございません・・・」「△△が大変失礼いたしました・・・」「上司のわたくし□□の監督不行き届きでございます・・・」と出力します。


部下がトラブルを起こさなかった場合、「△△君、よくやった!」「さすが私の右腕だ!」と出力します。


【Subordinateクラス】

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

  2. 社員名(String型)を引数として受け取るコンストラクタを定義します。
    スーパークラスEmployeeのnameフィールドに値を設定してください。

  3. 名前を取得するgetNameメソッド(引数なし、戻り値String型)を定義してください。

  4. 働くworkメソッドを実装します。
    「throws TroubleException」の記述も忘れないようにしてください。
    (部下はトラブルの責任を自分で取れないからです)


実行内容は「今回の○○はわたくし△△が担当いたします」「○○中・・・」と表示します。


ここからは2回に1回、キレてしまいます。

java.utilパッケージのRandomクラスをインスタンス化し、nextBooleanインスタンスメソッド(引数なし、戻り値boolean型)を呼び出します。
このメソッドはtrueとfalseを半々で返します。
trueの場合は、キレます。


キレた場合は「ふざけるな、バカ野郎!」とお客様に暴言を吐いてトラブル例外オブジェクトをスローします。


キレなかった場合は「今回の○○はわたくし△△が無事任務を果たしました」と表示してそのまま終了します。


【実行結果(キレた場合)】

さて、今回の得意先との取引は部下の有吉にまかせよう!

今回の得意先との取引はわたくし有吉が担当いたします
得意先との取引中・・・
ふざけるな、バカ野郎!

申し訳ございません・・・
有吉が大変失礼いたしました・・・
上司のわたくし上島の監督不行き届きでございます・・・


【実行結果(キレなかった場合)】

さて、今回の得意先との取引は部下の有吉にまかせよう!

今回の得意先との取引はわたくし有吉が担当いたします
得意先との取引中・・・
今回の得意先との取引はわたくし有吉が無事任務を果たしました

有吉君、よくやった!
さすが私の右腕だ!


【ExceptionInheritancePractice.java】

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

        // 部下クラスのインスタンスを生成
        Subordinate sub = new Subordinate("有吉");

        // 上司クラスのインスタンスを生成
        Boss boss = new Boss("上島", sub);

        // 上司の働くメソッドを実行
        boss.work("得意先との取引");

    }
}


// トラブル例外クラス
class TroubleException extends Exception {}


// 社員抽象クラス
abstract class Employee {

    // 社員名
    protected String name;

    // 働く抽象メソッド
    public abstract void work(String workName) throws TroubleException;

}


// ここに上司クラスを作成してください


// ここに部下クラスを作成してください


解答例

【ExceptionInheritancePractice.java】

import java.util.Random;

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

        // 部下クラスのインスタンスを生成
        Subordinate sub = new Subordinate("有吉");

        // 上司クラスのインスタンスを生成
        Boss boss = new Boss("上島", sub);

        // 上司の働くメソッドを実行
        boss.work("得意先との取引");

    }
}

// トラブル例外クラス
class TroubleException extends Exception {}

// 社員抽象クラス
abstract class Employee {

    // 社員名
    protected String name;

    // 働く抽象メソッド
    public abstract void work(String workName) throws TroubleException;

}

// 上司クラス
class Boss extends Employee {

    // 直属の部下フィールド
    private Subordinate sub;

    // コンストラクタ
    public Boss(String name, Subordinate sub) {
        this.name = name;
        this.sub = sub;
    }

    // 働くメソッド
    public void work(String workName) {
        System.out.println("さて、今回の" + workName +
                "は部下の" + sub.getName() + "に任せよう!");
        System.out.println();

        try {
            // 部下に仕事を任せる
            sub.work(workName);

            // 無事に仕事が終わった場合
            System.out.println(sub.getName() + "君、よくやった!");
            System.out.println("さすが私の右腕だ!");
        }
        // 業務上のトラブルが発生した場合
        catch(TroubleException e) {
            System.out.println("申し訳ございません・・・");
            System.out.println(sub.getName() + "が大変失礼致しました・・・");
            System.out.println("上司のわたくし" + name + "の監督不行き届きでございます・・・");
        }
    }
}

// 部下クラス
class Subordinate extends Employee {

    // コンストラクタ
    public Subordinate(String name) {
        this.name = name;
    }

    // 働くメソッド
    public void work(String workName) throws TroubleException {
        System.out.println("今回の" + workName + "はわたくし" +
                name + "が担当致します");
        System.out.println(workName + "中・・・");

        Random r = new Random();

        // キレた場合
        if(r.nextBoolean()) {

            // トラブル発生
            System.out.println("ふざけるな、ばか野郎!");
            System.out.println();

            // トラブル例外オブジェクトのスロー
            throw new TroubleException();
        }
        // キレなかった場合
        else {
            System.out.println("今回の" + workName + "はわたくし" +
                    name + "が無事任務を果たしました");
            System.out.println();
        }
    }

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


解説

オーバーライド、あるいは抽象メソッドを実装するには、メソッド名と引数の型・数の組み合わせ(シグネチャ)、そして戻り値の型をそろえる必要があります。
(厳密には、戻り値の型はサブタイプでも良いです。これを「共変戻り値」と言います)


では、throwsで例外クラスが指定されているときはどうすれば良いのでしょうか?



throwsで指定できる例外クラスには、「検査例外」「非検査例外」があります。

そしてthrowsに非検査例外のクラスを指定しても、そのメソッドを呼び出す側は例外処理(try-catchなど)が不要です。

なので、非検査例外をthrowsに含めるのは自由です。

何の規定もありません。



しかし検査例外の場合は、守らないといけないルールがあります。

スーパークラスのメソッドにthrowsの指定があり、それが検査例外の場合はオーバーライド(または抽象メソッドの実装)に次の3つのルールがあります。

  1. 同じ例外クラスをthrowsで指定する
  2. その例外クラスのサブクラスをthrowsで指定する
  3. throwsそのものを記述しない



項目1はとても分かりやすいです。

サブクラスでも同じ例外クラスをthrowsに記述するわけで、これが基本になります。


項目2は、理屈を考えると分かりやすいです。

たとえば、スーパークラス型の参照変数に対してメソッド呼び出しを書きます。

そのメソッドにはthrowsで例外クラスが書いてあるのでtry-catch文を書くことが義務付けられます。

そして、プログラムを実行してみると、その参照変数はサブクラスのインスタンスを指しており、そのメソッドはthrowsでサブクラス例外をスローします。

しかし、catchで指定している例外はスーパークラスの例外なので、サブクラスの例外でも補足可能なので問題ないわけです。


項目3は、意外に感じる人もいるかもしれません。

スーパークラスのメソッドはthrowsに検査例外を記述しており、メソッド呼び出し側に例外処理を義務付けるわけです。

なのに、オーバーライド(あるいは抽象メソッドの実装)をした方はthrowsを記述していないわけです。

でも、サブクラスのメソッドで検査例外をスローしないのであれば、それは最善とも言えます。

例外なんてスローしない方が良いわけですから。

ですので、throwsそのものを書かないのも有効になります。



結論としては、オーバーライド(あるいは抽象メソッドの実装)側で、勝手に検査例外のスローをthrowsで記述できない。

ここだけしっかり押さえておけば問題ないでしょう。



問題の解説に戻ります。

社員抽象クラスEmployeeがあり、働く抽象メソッドworkが定義してあります。

働くことにはトラブルがつきものですから、throwsで検査例外TroubleExceptionを指定します。


社員抽象クラスEmployeeのサブクラスは、上司Bossクラスと部下Subordinateクラスです。

BossクラスにはSubordinate型のフィールドを設けているのが今回のポイントです。


つまり、上司の仕事とは、部下に仕事を任せることだからです。


部下クラスの働くメソッドにはTroubleException例外スローの記述をします。

部下は仕事のトラブルの責任を自分で取れないからです。

部下クラスの働くメソッドを呼び出しているのは、上司クラスの働くメソッド内です。

上司は仕事のトラブルの責任を自分で取ることが可能なので、上司クラスの働くメソッドにはTroubleException例外スローの記述がないわけです。



今回は、Java標準クラスRandomを使用するようにしています。

このクラスはjava.utilパッケージのクラスなので、使用する際はインポート文をクラス定義の上に記述します。


ちなみに、Java標準クラスであるStringクラスSystemクラスIntegerクラスなどはjava.langパッケージのクラスです。

このパッケージはJavaの基本となるクラスを管理しており、インポート文を記述せずに使えるようになっています。
(デフォルトインポートされていると言います)



最後に小さな捕捉ですが、今回は部下Subordinateクラスに外部から名前を取得できるようにgeNameメソッドを定義しました。

上司が部下の名前を使うとき用にです。


しかし、社員抽象クラスEmployeeのnameフィールドのアクセス修飾子はprotectedになっています。


protectedは、サブクラスからもアクセス可を表現しますが、実は同一パッケージのクラスからもアクセス可能を意味します。


今回の問題ではパッケージ文によるパッケージングをしていませんが、その場合は無名パッケージといっておなじパッケージ扱いです。


上司Bossクラスと部下Subordinateクラスは同じパッケージのクラスなので、nameというprotectedフィールドには自由にアクセスできます。

// 上司クラス
class Boss extends Employee {

    // 直属の部下フィールド
    private Subordinate sub;

    // 働くメソッド
    public void work(String workName) {
        System.out.println("さて、今回の" + workName +
                "は部下の" + sub.name + "に任せよう!");
        System.out.println();
    }
}


しかし、Javaではオブジェクトのフィールドにアクセスする場合はゲッターメソッドやセッターメソッド、つまりアクセサメソッドを経由するのが基本だということも押さえておきましょう。


参考図書



LINE公式アカウント

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


LineOfficial

友だち追加



【まついのLINE公式アカウントはこちらから!】
Line公式アカウント