メソッドを上書き!オーバーライドをマスターしよう

docs

前に**継承(inheritance)について学んだよね。親クラスが持ってるものを子クラスが引き継ぐ、って話だった。すごく便利なんだけど、もし「親クラスのやり方じゃなくて、子クラス独自のやり方をさせたい!」ってなったらどうするんだろう?そこで登場するのが、今回のお題「オーバーライド(Override)」**だ。


オーバーライドって何?

簡単に言っちゃえば、親クラスで定義されているメソッドを、子クラスで自分の都合の良いように「上書き」することだよ。

「え、でも同じ名前のメソッドが親クラスにも子クラスにもあったら、どっちが呼ばれるの?」って疑問に思うよね。安心してください、子クラスでオーバーライドしたメソッドが優先的に呼ばれるんだ。まさに「上書き」だね。

なんでこんなことができるかというと、プログラムの世界では「もっとも具体的な指示」が優先されるからなんだ。親クラスは一般的な指示を出してるけど、子クラスは「私(子クラス)はこのメソッドに関してだけは、こう動きたいの!」とより具体的な指示を出してる、ってイメージすると分かりやすいんじゃないかな。


具体例で見てみよう!

じゃあ、実際にコードで見てみよう。 動物たちを例に考えてみるね。

まず、Animalという親クラスを作ってみよう。 動物なので、みんな「鳴く(makeSound)」ことができるはずだよね。

Java

// 親クラス:Animal.java
class Animal {
    String name; // 動物の名前

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

    // 動物が鳴くメソッド
    void makeSound() {
        System.out.println(name + "が何か鳴いています。");
    }
}

このAnimalクラスを継承して、Dog(犬)クラスとCat(猫)クラスを作ってみる。

Java

// 子クラス:Dog.java
class Dog extends Animal {
    // コンストラクタ
    Dog(String name) {
        super(name); // 親クラスのコンストラクタを呼び出す
    }

    // 犬が鳴くメソッド(makeSoundメソッドをオーバーライド!)
    @Override // オーバーライドしていることを明示するアノテーション
    void makeSound() {
        System.out.println(name + "がワンワンと鳴いています。");
    }
}

// 子クラス:Cat.java
class Cat extends Animal {
    // コンストラクタ
    Cat(String name) {
        super(name); // 親クラスのコンストラクタを呼び出す
    }

    // 猫が鳴くメソッド(makeSoundメソッドをオーバーライド!)
    @Override // オーバーライドしていることを明示するアノテーション
    void makeSound() {
        System.out.println(name + "がニャーニャーと鳴いています。");
    }
}

ここで注目してほしいのが、DogクラスとCatクラスのmakeSound()メソッドの前に付いている@Overrideだ。これはアノテーションって呼ばれるもので、「このメソッドは親クラスのメソッドをオーバーライドしてるよ!」って明示するためのものなんだ。これがあることで、プログラマーにも分かりやすいし、コンパイラ(Javaのコードをコンピューターがわかる言葉に翻訳してくれるもの)もチェックしてくれるから、書き間違いを防ぐこともできるんだ。絶対必要ではないけど、付けるのが一般的だよ。

じゃあ、実際に動かしてみよう!

Java

// メインクラス:Main.java
public class Main {
    public static void main(String[] args) {
        // Animalクラスのインスタンスを生成
        Animal genericAnimal = new Animal("普通の動物");
        genericAnimal.makeSound(); // 何が鳴くかな?

        System.out.println("---");

        // Dogクラスのインスタンスを生成
        Dog pochi = new Dog("ポチ");
        pochi.makeSound(); // 犬の鳴き声になるはず!

        System.out.println("---");

        // Catクラスのインスタンスを生成
        Cat tama = new Cat("タマ");
        tama.makeSound(); // 猫の鳴き声になるはず!
    }
}

このコードを実行すると、どうなると思う?

普通の動物が何か鳴いています。
---
ポチがワンワンと鳴いています。
---
タマがニャーニャーと鳴いています。

こんな感じで出力されるはずだ。 AnimalクラスのインスタンスであるgenericAnimalは、親クラスのmakeSound()メソッド(「何か鳴いています」)が呼ばれているよね。 でも、Dogクラスのインスタンスであるpochiは、親クラスのメソッドではなく、DogクラスでオーバーライドしたmakeSound()メソッド(「ワンワン」)が呼ばれているのが分かる。 Catクラスのtamaも同じだね。親クラスのmakeSound()ではなく、CatクラスでオーバーライドしたmakeSound()(「ニャーニャー」)が呼ばれているよ。

これがオーバーライドの力だ!親クラスで共通の動きを決めておいて、必要に応じて子クラスで独自の動きに「上書き」できるんだ。

vscodeで実行した結果


オーバーライドするときのルール

オーバーライドにはいくつかルールがあるんだ。これを守らないとエラーになっちゃうから気をつけよう。

  1. メソッド名が同じであること: 親クラスと子クラスで、メソッドの名前が完全に一致していなければいけないよ。
  2. 引数の型と数が同じであること: メソッドに渡す引数(かっこ()の中身)も、型と数が同じじゃないとダメだ。
  3. 戻り値の型が同じ、または互換性があること: メソッドが返す値の型(メソッド名の前にあるvoidとかintとか)も、基本的には同じじゃないといけない。Java 5以降では**共変戻り値(covariant return types)**といって、親クラスの戻り値のサブタイプ(子クラス)であればOKってルールもあるんだけど、今は「同じであること」って覚えておけば大丈夫。
  4. アクセス修飾子を弱くできない: 親クラスのメソッドがpublicなら、子クラスでオーバーライドするメソッドもpublicにするか、もっと広い範囲(protectedはOKだけどprivateはNGなど)にしないといけない。逆に、親クラスがprotectedなら子クラスではpublicでもprotectedでもOKだけど、privateにはできない、って感じだ。基本的には親クラスと同じか、よりアクセス範囲を広げることはできるけど、狭めることはできない、と覚えておけばOKだ。

例で言うと、void makeSound()void(戻り値の型)とmakeSound(メソッド名)と()(引数なし)が全て親クラスと同じになってるよね。アクセス修飾子も、今回は省略しているけど、デフォルト(パッケージプライベート)なので、同じアクセス範囲だ。


@Overrideアノテーションの重要性

さっきもちょっと触れたけど、@Overrideアノテーションはとても便利だよ。

  • コンパイラによるチェック: もしメソッド名や引数の型・数など、オーバーライドのルールを間違って書いてしまったら、コンパイラがエラーを出してくれるんだ。これがないと、ただの新しいメソッドとして扱われてしまって、オーバーライドしたつもりができてない!なんてことになりかねないから、デバッグが大変になるよ。
  • コードの可読性向上: 他の人がコードを見たときに、「あ、これは親クラスのメソッドを上書きしてるんだな」ってすぐにわかるから、コードが読みやすくなるんだ。

だから、オーバーライドするときは必ず@Overrideアノテーションを付けるようにしようね!


まとめ

今回はオーバーライドについて学んだよ。

  • オーバーライドとは?: 親クラスのメソッドを子クラスで「上書き」すること。
  • なぜ必要?: 継承したメソッドを、子クラス独自の振る舞いに変えたいときに使う。
  • ルール: メソッド名、引数、戻り値の型は基本的に親クラスと同じにする。アクセス修飾子を弱くすることはできない。
  • @Overrideアノテーション: オーバーライドしていることを明示し、コンパイラにチェックしてもらうために付けるべきもの。

継承とオーバーライドを組み合わせることで、共通の機能は親クラスにまとめて、特別な機能だけ子クラスで独自に定義する、という効率的なプログラムが書けるようになるんだ。これはオブジェクト指向プログラミングの大きなメリットの一つだよ。


 前回の記事へのリンクsuperキーワードで親クラスを操る! | ToolDocs

コメント

タイトルとURLをコピーしました