見出し画像

3分間プログラミング講座 第5回: 「共通の設計図」から「新しい設計図」を作る~継承~



登場人物:

  • チャット先生(通称:先生): オブジェクト指向マスター。普段は優しく、たまにユーモアも交えながら解説。

  • ボット助手(通称:ボット君): プログラミングを始めたばかりの若手。素朴な疑問を投げかけるのが得意。


ボット君: 先生!今日は「継承」について教えてくれるんですよね!「共通の設計図から新しい設計図を作る」って、なんだか秘密基地をパワーアップさせるみたいでワクワクします!

先生: ハハハ、またボット君らしい表現だね!まさにその「パワーアップ」や「共通部分の活用」が、今日のテーマ「継承(Inheritance)」の核心だよ。オブジェクト指向の三大要素(カプセル化、継承、ポリモーフィズム)の一つで、これもとても大切な概念なんだ。

ボット君: 三大要素、残りの一つですね!わくわくします!

先生: そうだよ。これまで「クラスは設計図、オブジェクトは実体」と学んできたね。そして「単一責任の原則」でクラスの役割を明確に分け、「カプセル化」でクラスの内部を安全に保つ方法を学んだ。継承は、これらの知識をさらに効率的に活用するための仕組みなんだ。


継承とは?~「親」から「子」へ受け継がれるもの~

先生: まずは、身近な例で考えてみよう。ボット君、君は「人間」だね。

ボット君: はい、そうです!

先生: じゃあ、人間にはどんな共通の特徴がある?

ボット君: えーと、二本足で歩く、言葉を話す、ご飯を食べる、みたいなことですか?

先生: その通り!では、人間の中にも「学生」や「社会人」や「先生」といった、いろいろな「種類」があるね。

ボット君: はい、僕も今は「学生」ボット君です!

先生: 素晴らしい!ここで考えてほしいのは、「学生」という種類は、「人間」という大きな枠組みが持っている特徴を、ほとんどそのまま持っている、ということだ。二本足で歩くし、言葉も話すし、ご飯も食べる。それに加えて、「学校に通う」「勉強する」といった「学生」ならではの特徴がある。

ボット君: なるほど!「人間」が持っていることを、そのまま受け継いで、さらに何か追加する、みたいな感じですか?

先生: まさにその感覚だ!プログラミングの世界でも、この「受け継ぐ」という仕組みを「継承(Inheritance)」と呼ぶんだ。

  • 共通する特徴を持つ元のクラスを「親クラス(スーパークラス、基底クラス)」と呼ぶ。

  • 親クラスの特徴を受け継ぎ、さらに独自の機能を追加・変更する新しいクラスを「子クラス(サブクラス、派生クラス)」と呼ぶ。

先生: 以降、分かりやすさのため「親クラス」「子クラス」と呼ぶことにするね。 こうすることで、親クラスに書かれた共通のデータやメソッドを、子クラスで繰り返し書かずに済むんだ。


継承の書き方と例

先生: Javaでは、extendsというキーワードを使って継承を表すよ。

Java

// 親クラス:Car (車の共通の設計図)
public class Car {
    private String color;
    private String maker;
    private int speed;

    public Car(String color, String maker) {
        this.color = color;
        this.maker = maker;
        this.speed = 0;
        System.out.println(this.maker + "製の" + this.color + "の車が作られました!");
    }

    // getterメソッド(省略)
    public String getColor() { return this.color; }
    public String getMaker() { return this.maker; }
    public int getSpeed() { return this.speed; }

    // setterメソッド(一部省略)
    public void setSpeed(int speed) {
        if (speed < 0) {
            System.out.println("速度は負の値にできません。");
            // 実際の開発では、不正な値に対しては例外を投げることも多いよ(例:throw new IllegalArgumentException("速度は負の値にできません。");)。
            return;
        }
        if (speed > 300) {
            System.out.println("速度上限を超えています。300km/hに設定します。");
            this.speed = 300;
            return;
        }
        this.speed = speed;
        System.out.println("速度を" + speed + "km/hに設定しました。");
    }

    public void accelerate(int accelerationSpeed) {
        setSpeed(this.speed + accelerationSpeed);
    }

    public void brake() {
        setSpeed(0);
        System.out.println(this.maker + "の車が停止しました。");
    }
}

// 子クラス:ElectricCar (電気自動車の設計図)
// Carクラスの特徴を全て継承する
public class ElectricCar extends Car {
    private int batteryLevel; // 電気自動車独自の属性

    // コンストラクタ
    public ElectricCar(String color, String maker, int batteryLevel) {
        // 親クラス(Car)のコンストラクタを呼び出す
        super(color, maker);
        this.batteryLevel = batteryLevel;
        System.out.println("これは電気自動車です。バッテリー残量:" + this.batteryLevel + "%");
    }

    // 電気自動車独自のメソッド
    public void charge() {
        this.batteryLevel = 100;
        System.out.println("バッテリーを充電しました。残量:" + this.batteryLevel + "%");
    }

    // batteryLevelのgetter
    public int getBatteryLevel() {
        return this.batteryLevel;
    }
}

ボット君: おお!extends Carって書くだけで、Carクラスの色とか速度とか、加速・停止のメソッドまで使えるようになるんですか!?すごい!バッテリーの機能はElectricCar独自のものなんですね!

先生: その通り!ElectricCarクラスは、Carクラスが持っているpublic、protected、そしてpackage-private(デフォルト)な属性やメソッドを受け継ぐんだ。ただし、privateなメンバーは継承されないよ。

先生: super(color, maker);という部分も重要だよ。これは、子クラスのコンストラクタから、親クラスのコンストラクタを呼び出すためのものなんだ。親が持つ共通の初期設定を、子も利用することで、コードの重複を防ぐことができる。

先生: 実際に使ってみると、こんな感じだよ。

Java

public class Main {
    public static void main(String[] args) {
        // 使用例
        ElectricCar tesla = new ElectricCar("白", "Tesla", 80);
        
        // 親クラスから継承したメソッドを使用
        tesla.accelerate(50);
        System.out.println("現在の速度: " + tesla.getSpeed() + "km/h");
        
        // 子クラス独自のメソッドを使用
        tesla.charge();
        
        tesla.brake();
    }
}

継承のメリット

先生: この継承という仕組みは、どんなメリットをもたらすと思う?

ボット君: えーと、共通のコードを何回も書かなくて済むから、楽!ですか?

先生: その通り!それが一番大きなメリットの一つだね。

  • コードの再利用性: 親クラスに共通の機能を集約することで、子クラスで同じコードを何度も書く必要がなくなる。

  • 保守性・管理性の向上: 共通の機能にバグが見つかった場合、親クラスだけを修正すれば、それを継承している全ての子クラスに修正が反映される。

  • 拡張性: 既存の親クラスを壊すことなく、新しい子クラスを追加して機能を拡張できる。

  • 構造化: クラス間の関係(「AはBの一種である」という関係、これを「is-a」関係と呼ぶよ)を明確に表現でき、プログラム全体の構造が分かりやすくなる。

ボット君: なるほど!Carクラスを修正すれば、ElectricCarもGasolineCarも、全部自動で修正されるってことですね!それは便利だ!

先生: その通り!これはまさに「共通の設計図」を元に「専門化された新しい設計図」を作る、というイメージだよ。


継承使用時の注意点

先生: とても便利な継承だけど、いくつか注意点もあるよ。

  • Javaでは単一継承のみサポート: 一つのクラスは、直接的に一つの親クラスからしか継承できないんだ。

  • privateなメンバーは継承されない: 親クラスのprivateな属性やメソッドは、子クラスからは直接アクセスできない。

  • 継承関係は「is-a」関係が成り立つ場合に使用する:

    • 継承は、「AはBの一種である」という関係(is-a関係)が成り立つときに使うのが適切だよ。

    • 例えば、「犬は動物の一種である(Dog is an Animal)」は正しい関係だね。

    • もし、「車はエンジンの一種である」のような関係で継承を使おうとすると、設計がおかしくなってしまうんだ。車はエンジンを「持っている」けれど、「エンジンの一種」ではないからね。


メソッドのオーバーライド~「親」の行動を「子」がアレンジ~

先生: 継承にはもう一つ、とても強力な機能があるんだ。例えば、普通の車と電気自動車で、加速の仕方が少し違う、なんてことがあるかもしれないね。

ボット君: ああ、エンジン音じゃなくてモーターの音がするとか!

先生: そうだね。そんな時に、子クラスが親クラスから受け継いだメソッドの中身だけを、子クラス独自の内容に「上書き(アレンジ)」することができるんだ。これを「オーバーライド(Override)」と呼ぶよ。

Java

// ElectricCarクラスに追記
public class ElectricCar extends Car {
    // ... (既存のコード) ...

    @Override // オーバーライドであることを示すアノテーション(注釈)
    // このアノテーションにより、コンパイル時にオーバーライドの正確性がチェックされる
    public void accelerate(int accelerationSpeed) {
        // 親クラスのaccelerate()とは違う、電気自動車独自の加速処理
        if (getBatteryLevel() > 10) { // バッテリーがあるか確認
            setSpeed(getSpeed() + accelerationSpeed);
            // バッテリー残量の変更は、表示と分離して行おう。
            this.batteryLevel -= 5;
            System.out.println("静かに加速します!現在バッテリー残量:" + this.batteryLevel + "%");
        } else {
            System.out.println("バッテリーが少ないため加速できません!");
        }
    }
}

ボット君: わー!@Overrideって書くと、同じ名前のaccelerateメソッドでも、電気自動車ならではの動きになるんですね!バッテリーの残量も減るんだ!

先生: その通り!共通の「加速する」という行動は持っているけど、その具体的な「加速の仕方」は電気自動車独自のものにできるんだ。

先生: ただし、オーバーライドする際は、メソッド名、引数の型と数は親クラスのメソッドと同じでなければならない戻り値の型は、親クラスと同じか、そのサブタイプ(より具体的な型)でなければならないというルールがあるから注意してね。これもまた、オブジェクト指向の柔軟性を高める重要な機能だよ。


今日のまとめ!

  • 継承(Inheritance)

    • 既存のクラス(親クラス)の特徴(データやメソッド)を新しいクラス(子クラス)が受け継ぎ、さらに独自の機能を追加・変更する仕組み。

    • キーワード:extends を使って継承する。子クラスのコンストラクタからは super() で親クラスのコンストラクタを呼び出す。

  • メリット

    • コードの再利用性:共通部分を何度も書かずに済む。

    • 保守性・管理性の向上:親クラスの修正が子クラスに反映される。

    • 拡張性:既存コードを壊さず機能を追加できる。

    • 構造化:クラス間の「is-a(~の一種である)」関係を明確にする。

  • 注意点: 単一継承、privateメンバーは継承されない、is-a関係が重要。

  • オーバーライド(Override)

    • 親クラスから受け継いだメソッドの実装(中身)を、子クラスで独自に上書きすること。

    • @Overrideアノテーション(注釈)を付けて示すことが多い。これによりコンパイル時に正確性がチェックされる。

    • メソッド名、引数の型と数は親クラスと同一である必要があり、戻り値の型は親クラスと同じか、そのサブタイプでなければならない


ボット君: 先生、今日は「継承」について、とてもよく分かりました!共通の設計図から新しい設計図を作ることで、すごく効率的にプログラムが書けるようになるんですね!オーバーライドもすごい!

先生: その調子だ、ボット君!これでオブジェクト指向の三大要素である「カプセル化」「継承」「ポリモーフィズム」のうち、二つまで理解したことになる。次回は、最後の「ポリモーフィズム」について学んでいこう。継承したクラスたちが、まるで変身ロボットのように、状況に応じて様々な姿を見せる、そんな面白い話だよ。

ボット君: 変身ロボット!ますます面白そうです!楽しみです!ありがとうございました!


次回:第6回:「色々な形」に「同じように接する」~ポリモーフィズム


いいなと思ったら応援しよう!

ナオユキ|物語と知識のコンシェルジュ もしこの記事が少しでも役に立ったり、楽しんでいただけたりしたら、チップをいただけるととても嬉しいです! いただいたチップは、今後のより良いコンテンツ制作のための書籍代📚とnote購入🗒️、他の方へのチップ代💰に使わせていただきます。 いつも応援ありがとうございます!