👩🏼‍🏫

【Java】継承とオーバーライド

2022/02/13に公開

はじめに

Java Silverの受験にあたって、勉強したことをまとめました。
だいぶ長くなったので、覚悟して読んでください…😂

1. 継承とは

既存のクラスを元に、新しいクラスを定義すること

  • 元となるクラス:スーパークラス(親クラス)
  • 新しく定義されるクラス:サブクラス(子クラス)
  • サブクラスをインスタンス化すると、スーパークラスで定義した変数やメソッドは、すべてサブクラスに引き継がれる

継承の定義

  • extendsを使用する
アクセス修飾子 class クラス名 extends 元となるクラス名 {
    元となるクラスとの差分メンバ
}
メンバとは

クラス内に宣言された、フィールドとメソッドのこと。
フィールド:クラス内に宣言された変数(属性)。
メソッド:処理(操作)。

多重継承の禁止

Javaでは、extendsのあとに指定できるクラスは1つだけ

  • 1つのスーパークラスから、複数のサブクラスを作ること → OK
  • 1つのサブクラスから、さらにサブクラス(孫クラス)を作ること → OK
  • 複数のスーパークラスから、1つのサブクラスを作ること → NG
NG例 (コンパイルエラー)

public class C extends A, B {
    なんらかのコード
}

継承できないクラス

宣言時にfinalがついているクラスは継承できない!

  • finalがついているクラスを継承元にすると、コンパイルエラーになる
  • Stringクラスは宣言時にfinalがついているので継承できない
APIリファレンスでのStringクラスの宣言
public final class String extends Object
implements Serializable, Comparable<String>, CharSequence
  • 自分で作成したクラスも、finalをつければ継承禁止にできる
Sample.java
public final class Sample {
    // フィールド
    // メソッド
}

2. オーバーライド

継承したスーパークラスのメソッドの内容を、サブクラスで上書き(再定義)すること

  • スーパークラスで定義したメソッドと目的が同じで、処理が異なるメソッドを定義する場合に使う
  • サブクラスがインスタンス化され、オーバーライドされたメソッドが呼び出されたとき
    サブクラスで上書きしたメソッドが優先的に呼び出される
サンプルコード
スーパークラス側
public void attack(Slime m) {
    System.out.println(this.name + "は攻撃した!");
    m.hp -= 10;
    System.out.println("敵に10のダメージをあたえた");
}
サブクラス側
@Override
public void attack(Slime s) {
    System.out.println(this.name + "はめちゃくちゃ攻撃した!");
    s.hp -= 30;
    System.out.println("敵に30のダメージをあたえた");
};
Main.java
public class Main {
    public static void main(String[] args) {
        // サブクラスのインスタンスを生成
        SuperHero sh = new SuperHero();
        // 敵キャラのインスタンスを生成
        Slime s = new Slime();
        // メソッド呼び出し
        sh.attack(s);
    }
}
実行結果
// サブクラスで上書き(オーバーライド)したメソッドが呼び出される
akariはめちゃくちゃ攻撃した!
敵に30のダメージをあたえた

オーバーライドの条件

  • メソッド名と引数の型・数・順番が、スーパークラスと同じ
  • アクセス修飾子はスーパークラスと同じか、それより緩いもの

↓ アクセス修飾子についてまとめた記事
https://zenn.dev/miya_akari/articles/7012029ec5455d

@Override (アノテーション)

  • 「このメソッドはオーバーライドしているよ」というしるし
  • オーバーライドする側(サブクラス) のメソッドの前につける
  • @Overrideをつけると、スーパークラスとメソッド名や引数が異なるとき、コンパイルエラーにしてくれる
    「オーバーライドしたつもりができていない」を防ぐ!
スーパークラス側
public void attack() {
    なんらかの処理
}
サブクラス側
@Override
public void atack() {
    なんらかの処理
};
// メソッド名がスーパークラスと異なるので、コンパイルエラー!

オーバーライドを禁止する方法

  • クラスは継承してもいいけど、メソッドはオーバーライドしてほしくないとき
    メソッドにfinalをつける
Hero.java
public class Hero {
    String name;
    int hp;

    // 以下はオーバライドできない
    public final void run() {
        なんらかの処理
    }

    // 以下はオーバーライドできる
    public void attack() {
        なんらかの処理
    }
}

3. 継承・オーバーライドをやってみる

ちょっと長いけど、実際に書くとこんな感じ

Hero.java
// スーパークラスの定義
public class Hero {
    String name = "akari";
    int hp = 100;

    public void attack(Slime m) {
        System.out.println(this.name + "は攻撃した!");
        m.hp -= 10;
        System.out.println("敵に10のダメージをあたえた");
    }

    // オーバーライド禁止
    public final void run() {
        System.out.println(this.name +"は逃げた");
    }
}
SuperHero.java
// サブクラスの定義
public class SuperHero extends Hero{
    // 「Heroクラスを継承したSuperHeroクラスをpublicで定義」と読むと分かりやすい

    // 追加したメソッド
    public void sleep() {
        this.hp = 100;
        System.out.println(this.name + "は眠ってすっかり回復した");
    }

    // オーバライド(上書き)したメソッド
    // スーパークラスとメソッド名や引数リストが同じで、処理だけが違う
    @Override
    public void attack(Slime s) {
        System.out.println(this.name + "はめちゃくちゃ攻撃した!");
        s.hp -= 30;
        System.out.println("敵に30のダメージをあたえた");
    };
}
Main.java
public class Main {
    public static void main(String[] args) {
        // スーパークラスのインスタンスを作成
        Hero h = new Hero();

        // 敵キャラのインスタンスを作成
        Slime s1 = new Slime();

        // スーパークラスのメソッドを呼び出す
        h.attack(s1);


        // サブクラスのインスタンスを作成
        SuperHero sh = new SuperHero();

        // 敵キャラのインスタンスを作成
        Slime s2 = new Slime();

        // サブクラスのメソッドを呼び出す
        sh.attack(s2); // オーバーライドしたメソッド
        sh.sleep(); // 追加したメソッド
    }
}
実行結果
// スーパークラス
akariは攻撃した!
敵に10のダメージをあたえた

// サブクラス
akariはめちゃくちゃ攻撃した!
敵に30のダメージをあたえた
akariは眠ってすっかり回復した

※敵キャラのコード(Slime.java)は省略

4. サブクラスのインスタンス構造

  • サブクラスの内側にスーパークラスがあるイメージ
  • サブクラスでメソッドをオーバーライドしても、スーパークラスのメソッドが消えてなくなるわけではない
  • インスタンスの外側からメソッド呼び出しがあった場合、外側にあるサブクラス部分のメソッドで対応しようとするだけ

superキーワード

  • サブクラスからスーパークラスのメソッドを呼びたいときは、superを使用する
  • superは、今より1つ内側のインスタンス部分をあらわす
使用方法
// サブクラスからスーパークラスのフィールドを呼び出すとき
super.フィールド名;

// サブクラスからスーパークラスのメソッドを呼び出すとき
super.メソッド名(引数);

5. 継承とコンストラクタ

  • Javaでは、「すべてのコンストラクタは必ずその先頭でスーパークラスのコンストラクタを呼び出さないといけない」というルールがある
  • サブクラスのコンストラクタからスーパークラスのコンストラクタを呼び出すときは、super(引数)を使用する
サブクラス側
class B extends A{
  B(){
    super(); // ①ここでスーパークラスのコンストラクタを呼び出し、
    System.out.println("クラスBのコンストラクタ"); // ②その後サブクラスのコンストラクタが呼び出される
  }
}

暗黙のコンストラクタ呼び出し

サブクラスのコンストラクタで、スーパークラスのコンストラクタ呼び出しを記述していない場合は、コンパイラが自動的に引数なしのsuper();を追加する[1]

↓ 以下の記事の解説が分かりやすい!
https://www.javadrive.jp/start/extends/index4.html

まとめ

  • 継承の構文は、アクセス修飾子 class クラス名 extends 元となるクラス名
  • Javaでは、多重継承は禁止
  • 継承したスーパークラスのメソッドの内容を、サブクラスで上書き(再定義)することをオーバーライドという
  • サブクラスがインスタンス化され、オーバーライドされたメソッドが呼び出されたときは、サブクラスで上書きしたメソッドが呼び出される
  • サブクラスからスーパークラスのメソッドを呼びたいときは、super.メソッド名;
  • サブクラスからスーパークラスのフィールドを呼びたいときは、super.フィールド名;
  • サブクラスをインスタンス化すると必ずスーパークラスのコンストラクタが実行されてから、サブクラスのコンストラクタが実行される

長い!多い!!難しい!!!😭

参考

https://honto.jp/isbn/978-4-295-00780-7?partnerid=aftoc1904996260&booktype=ebook
https://honto.jp/isbn/978-4-7981-6204-1?partnerid=aftoc1904996260&booktype=ebook

脚注
  1. この場合、スーパークラスに引数なしのコンストラクタが定義されていないとコンパイルエラーになるので注意 ↩︎

Discussion