読み込み中...

Javaでマスターする!プログラミング初心者向けオーバーライドのたった7ステップ

Javaプログラミング初心者が学ぶオーバーライドの基本と応用方法 Java
この記事は約24分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

Javaのオーバーライドは、親クラスのメソッドを子クラス側で再定義し、同じ呼び出し方のまま振る舞いを切り替える仕組みです。プログラミング初心者がJavaプログラミング学習でつまずきやすいのは、継承多態性@Overrideの関係が一度に出てくる点だと言えるでしょう。

そのため、最初に短いサンプルコードで結論を押さえ、続けてJavaの特性、オーバーライドの使い方、注意点、応用的なコーディング技法へ進む流れにしています。プログラム作成の途中で「親クラスの処理を残すべきか」「子クラスだけで処理を差し替えるべきか」を判断できると、クラス設計がかなり読みやすくなります。

動作確認環境
  • Java SE 21 / JDK 21
  • コンパイル例は標準のjavacjavaコマンドを想定
  • 外部ライブラリなし
📖 この記事で学べること
  • Javaでextendsを使った継承とオーバーライドの関係
  • @Overridesuperabstractを使う判断基準
  • プログラミング初心者が間違えやすいprivatestaticの扱い
  • サンプルコードを通じた多態性と動的メソッドディスパッチの読み方
  • プログラム作成で再利用しやすいコーディング技法の整理

Javaとは?

結論から言えば、Javaはclassを中心にプログラムを組み立てるオブジェクト指向言語であり、オーバーライドはその設計を実用的にする中核的な仕組みです。たとえば、次のサンプルコードではAnimal型の変数にDogのインスタンスを入れても、呼び出されるのはDog側のgreetになります。

class Animal {
    void greet() {
        System.out.println('動物です');
    }
}

class Dog extends Animal {
    @Override
    void greet() {
        System.out.println('犬です');
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.greet();
    }
}

結果: 期待される出力は「犬です」です。変数の宣言型はAnimalですが、実体がDogなので、実行時に子クラス側のgreetが選ばれます。

この動きはJavaのJVM上で処理される仕組みと、オブジェクト指向のポリモーフィズムに支えられています。公式ドキュメントとしては、OracleのOverriding and Hiding Methodsが、オーバーライド時の@Override利用に触れているのが基本です。

一般に、Javaは業務アプリケーション、Webアプリケーション、Android関連の学習、サーバーサイド開発などで使われますし、ここがポイントです。Javaプログラミング学習ではpublicprotectedprivatepackage-privateの違いが後から効いてくるため、オーバーライドと同時にアクセス修飾子も整理しておくと理解が進みます。

これに加えて、Javaは標準APIが広く、ObjectStringListArrayListMapなどを組み合わせながらプログラム作成を進められますし、ここがポイントです。コレクションの基礎はJava List型完全ガイドと関連が深く、オーバーライドの学習後に読むと型の扱いを接続しやすいでしょう。

プログラム言語としての特性

Javaの特性を理解すると、オーバーライドが単なる文法ではなく、プログラム全体の拡張性を支える設計手段だと分かります。特に押さえたいのは、JVMによる実行、classベースの構造、interfaceabstract classの使い分け、例外処理、標準ライブラリの広さです。

項目関連する構文オーバーライドとの関係プログラミング初心者の注意点
クラスclassメソッドを持つ基本単位ファイル名とpublic class名を合わせる
継承extends親の処理を子へ引き継ぐ継承しすぎると構造が読みにくい
インターフェイスinterface共通の呼び出し口を作る実装はimplementsで結ぶ
抽象クラスabstract一部を子クラスに任せる直接newできない
メソッドvoid再定義の対象になる戻り値と引数を確認する
注釈@Override再定義ミスをコンパイル時に検出付け忘れると誤字に気づきにくい
親呼び出しsuper親の処理を残して拡張する呼び出し順で結果が変わる
生成new実体の型が呼び出し先を決める宣言型と実体型を区別する
アクセス制御public子で狭くできないprivateはオーバーライド対象外
保護範囲protected継承先から利用しやすいむやみに広げない
パッケージpackage同一パッケージ内の可視性に影響ディレクトリ構成と合わせる
インポートimport外部型を使いやすくする未使用importを残さない
戻り値return互換性のある型が必要戻り値だけを変えても別メソッドにはならない
例外throws親より広い検査例外は投げにくい例外階層を確認する
不変化finalメソッドの再定義を禁止設計意図があるときに使う
静的メソッドstatic隠蔽であり通常のオーバーライドではないインスタンスメソッドと混同しない
コンストラクタconstructorオーバーライドできない親の初期化はsuper()で扱う
文字列StringtoStringの再定義で表示を整える連結が増えたら可読性を確認する
等価性equals値比較の意味を変えるhashCodeと合わせる
ハッシュ値hashCode集合やMapの動きに影響片方だけ変えない
表示用文字列toStringログやデバッグ表示を改善機密値を出さない
配列String[]mainの引数で使う添字範囲に注意する
エントリポイントmainサンプルコードの開始地点シグネチャを崩さない
標準出力System.out.println挙動確認に使う本番ログとは分けて考える
型変換instanceof実体型の判定に使える過剰な分岐は設計を見直す
列挙型enum状態を限定できる文字列より安全に扱える
ジェネリクス<T>型安全な共通処理に使う型消去の制約を知る
ラムダ->関数型の書き方と併用できる継承設計とは目的が違う
レコードrecord値オブジェクトを短く書ける自動生成メソッドの意味を確認する
コンパイルjavacシグネチャの誤りを検出エラー文を読む習慣を付ける

そのうえで、Javaのプラットフォーム独立性はbytecodeJVMにより成り立ちます。ソースコードをjavacでコンパイルすると.classファイルが作られ、実行環境側のJVMがその中間形式を処理する構造です。

一方、オブジェクト指向性はカプセル化継承多態性を組み合わせて、変更に耐えやすいプログラム作成を助けます。オーバーライドは多態性の代表例であり、同じrundrawという呼び出しでも、実体のクラスに応じて処理が変わりますが、これは押さえたい点です。

ただし、継承を使えば常に設計が良くなるわけではありません。処理を少しだけ共通化したい場合はinterface、固定の処理順だけ共有したい場合はabstract class、状態を持たない変換処理なら通常のユーティリティクラスという選択肢もあるのが基本です。

公式仕様に近い説明を確認したい場合は、OracleのJava Language Specification 8.4.8が、継承、オーバーライド、隠蔽の要件を扱っています。Javaプログラミング学習では、チュートリアルで感覚をつかみ、仕様で例外的なルールを確認する順が現実的です。

オーバーライドとは?

オーバーライドとは、親クラスまたはインターフェイスで宣言されたインスタンスメソッドを、子クラスで同じシグネチャのまま再定義することです。シグネチャはメソッド名引数リストで決まり、戻り値は互換性のある型である必要があるのが目安です。

これにより、呼び出す側はAnimalVehiclePaymentのような抽象度の高い型だけを見ればよくなります。その型の向こう側にDogCarCreditCardPaymentがあっても、同じメソッド名で処理を呼び出せる点が扱いやすいところです。

💡 Tips: オーバーライドとオーバーロードは名前が似ているのが目安です。オーバーライドは親子関係で同じシグネチャを再定義し、オーバーロードは同じクラス内などで引数の違う同名メソッドを並べる仕組みです。

具体的には、@Overrideを付けたメソッドが親のメソッドと対応していなければ、コンパイラがエラーを出するのがポイントです。OracleのOverrideアノテーションのAPIドキュメントでも、意図したオーバーライドでない場合にコンパイルエラーになることが説明されています。

そのため、サンプルコードを書くときだけでなく、実際のプログラム作成でも@Overrideは省略しない書き方が一般的です。プログラミング初心者ほど、greetgreatと書き間違えるような小さなミスに気づきにくいため、注釈でコンパイラに確認させる価値があります。

一方、staticメソッドはインスタンスに紐づく動的な再定義ではなく、同名メソッドの隠蔽として扱われますし、これが一つの目安です。finalメソッドは意図的に再定義を禁止するため、拡張してほしくない処理や契約を固定したい処理に使われますが、これは押さえたい点です。

こうした区別を早い段階で押さえると、オーバーライドを使うべき場所と避けるべき場所が見えてきます。Javaプログラミング学習では、文法暗記よりも「同じ呼び出し名で異なる振る舞いを差し替える」という設計上の目的を中心に整理すると理解しやすいでしょう。

オーバーライドの基本的な使い方

基本形は、親クラスにあるメソッドを子クラスで同じ名前と引数にそろえて書き、先頭に@Overrideを付ける流れです。アクセス修飾子は親より狭くできないため、親がprotectedなら子はprotectedまたはpublicにするのが一般的です。

サンプルコード1:簡単なオーバーライドの方法

これを最小構成で書くと、親クラスAnimalgreetを子クラスDogで差し替える形になります。サンプルコードではpublic class Mainだけを公開クラスにし、同じファイルに補助クラスを置く構成にしています。

class Animal {
    public void greet() {
        System.out.println('こんにちは、私は動物です。');
    }
}

class Dog extends Animal {
    @Override
    public void greet() {
        System.out.println('こんにちは、私は犬です。');
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.greet();
    }
}

結果: 期待される出力は「こんにちは、私は犬です。」です。Dogのインスタンスからgreetを呼ぶため、親ではなく子クラス側の処理が選ばれます。

このとき、@Overrideがあることで、親クラスに対応するgreetが存在するかをコンパイラが確認します。もし引数をうっかり追加してgreet(String name)に変えると、それは別シグネチャになるため、意図したオーバーライドではありません。

その違いは、オーバーロードとの混同を防ぐうえでも役立ちますが、覚えておくと役立つでしょう。オーバーロードはprint(String text)print(int value)のように引数違いで同名メソッドを並べる書き方であり、親子関係がなくても成立するのがポイントです。

サンプルコード2:親クラスのメソッドを利用する方法

その処理を完全に置き換えるだけでなく、親の処理を残したまま子クラスで処理を足すこともあります。こうしたコーディング技法ではsuperを使い、共通処理を親へ集めて重複を減らするのが現実的です。

class Vehicle {
    public void speedUp() {
        System.out.println('車両の速度を上げます');
    }
}

class Car extends Vehicle {
    @Override
    public void speedUp() {
        super.speedUp();
        System.out.println('カー専用の加速機能を追加します');
    }

    public void playMusic() {
        System.out.println('音楽を再生します');
    }
}

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        myCar.speedUp();
        myCar.playMusic();
    }
}

結果: 期待される出力は「車両の速度を上げます」「カー専用の加速機能を追加します」「音楽を再生します」の順です。super.speedUp()で親の処理を先に呼び、その後に子クラス独自の処理を加えています。

このパターンは、ログ出力、権限確認、前後処理、共通バリデーションなどを親に置きたい場合に使われます。ただし、親の処理が何を保証しているか分からないままsuperを呼ぶと、処理順の依存が増えるかもしれません。

そのため、プログラム作成では親クラスのメソッド名だけでなく、事前条件、事後条件、副作用もコメントや命名で読み取れるようにすると整理できるのが一般的です。Javaの注釈そのものに関心がある場合は、Javaアノテーションの12選も関連します。

オーバーライドの応用例

応用では、親型の変数に複数の子クラスを入れ、同じメソッド呼び出しで異なる処理を走らせます。この考え方は、一覧処理、描画処理、決済処理、通知処理など、プログラム作成のさまざまな場面で現れますし、ここを基本と考えるとよいでしょう。

サンプルコード3:多態性を活用したオーバーライド

具体的には、Animal型の配列にDogCatを入れ、ループでvoiceを呼ぶと分かりやすいです。呼び出し側は動物ごとの分岐を書かず、各クラスのオーバーライドに処理を任せますし、これが一つの目安です。

class Animal {
    void voice() {
        System.out.println('動物の声');
    }
}

class Dog extends Animal {
    @Override
    void voice() {
        System.out.println('ワンワン');
    }
}

class Cat extends Animal {
    @Override
    void voice() {
        System.out.println('ニャーン');
    }
}

public class Main {
    public static void main(String[] args) {
        Animal[] animals = {new Animal(), new Dog(), new Cat()};

        for (Animal animal : animals) {
            animal.voice();
        }
    }
}

結果: 期待される出力は「動物の声」「ワンワン」「ニャーン」の順です。配列の要素型はAnimalですが、実体のクラスに応じたvoiceが呼ばれます。

この構造にすると、新しくBirdを追加しても、呼び出し側のループはほとんど変わりません。変更点が各クラスに閉じるため、コーディング技法としてのオーバーライドが読みやすさにもつながります。

サンプルコード4:抽象クラスを使ったオーバーライド

一方、親クラス側に標準の処理を書くのではなく、子クラスに実装を強制したい場合はabstractを使いると理解できるのが現実的です。抽象メソッドは本体を持たず、継承先でオーバーライドされることを前提にした宣言です。

abstract class Animal {
    abstract void sound();
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println('ワンワン');
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println('ニャー');
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        myDog.sound();
        myCat.sound();
    }
}

結果: 期待される出力は「ワンワン」「ニャー」の順です。Animalは抽象クラスなので直接生成せず、DogCatの実装を通じてsoundを呼びます。

これにより、親クラスは「音を出す」という契約だけを決め、具体的な音は子クラスへ任せられます。Javaプログラミング学習では、抽象クラスを「共通処理も持てる親」、インターフェイスを「外部から見た約束」と整理すると扱いやすいです。

ただし、抽象クラスに状態や共通処理を増やしすぎると、子クラスが親の都合に縛られますし、ここがポイントです。状態を共有する理由が薄い場合はinterfaceや委譲を検討すると、後から差し替えやすい構造になるでしょう。

オーバーライドの注意点と対処法

注意点の中心は、シグネチャ、アクセス修飾子、例外、staticfinalの扱いです。プログラミング初心者がエラーに見える結果へ遭遇したときは、構文よりも「本当にオーバーライドになっているか」を先に確認すると切り分けやすくなります。

⚠️ 注意: 動作未確認のコードを「実行できた」と断定するのは避けます。学習用の出力は、コンパイル可能な構造を示したうえで「期待される出力」として読むのが適切です。

アノテーションの利用

これを防ぐ最も単純な方法が@Overrideの付与です。メソッド名、引数、アクセス修飾子、戻り値の互換性に問題がある場合、コンパイル時点で誤りを発見しやすくなると覚えるとよいでしょう。

class Parent {
    void message() {
        System.out.println('親クラスのメソッド');
    }
}

class Child extends Parent {
    @Override
    void message() {
        System.out.println('子クラスのメソッド');
    }
}

結果: 期待される動きは、Childのインスタンスからmessageを呼ぶと子クラス側の文言が出力されることです。@Overrideにより、親のmessageと対応しているかをコンパイラが確認します。

そのため、サンプルコードを写す段階でも@Overrideを省略しないほうが安全です。Javaプログラミング学習では、コンパイルエラーを失敗ではなく、誤った設計を早く見つける手がかりとして扱うとよいでしょう。

適切なアクセス修飾子の選定

アクセス修飾子では、子クラス側で公開範囲を狭められない点がよく問題になります。親がprotectedなら子はprotectedまたはpublicにできる一方、privateにはできません。

class Parent {
    protected void message() {
        System.out.println('親の処理');
    }
}

class Child extends Parent {
    @Override
    public void message() {
        System.out.println('子の処理');
    }
}

結果: 期待される動きは、Child側のmessageが正しいオーバーライドとして扱われることです。protectedからpublicへ広げているため、可視性の制約に反しません。

逆に、privateメソッドは子クラスから見えないため、同じ名前で書いても親のメソッドを上書きしたことにはなりません。この違いを知らないと、呼び出し元によって期待と異なる処理が選ばれる原因になります。

superキーワードの活用方法

superは、親クラスの同名メソッドを明示的に呼びたいときに使います。子クラスで前処理や後処理を足しながら親のロジックを保持したい場合、重複を減らせるコーディング技法になると考えられますが、覚えておくと役立つでしょう。

class Parent {
    void message() {
        System.out.println('親クラスのメソッド');
    }
}

class Child extends Parent {
    @Override
    void message() {
        super.message();
        System.out.println('子クラスのメソッド');
    }
}

public class Main {
    public static void main(String[] args) {
        Child child = new Child();
        child.message();
    }
}

結果: 期待される出力は「親クラスのメソッド」「子クラスのメソッド」の順です。super.message()が先に親の処理を呼び、その後で子クラスの処理を続けます。

ただし、superの多用は親子の結合を強めます。親の処理順を変えると子にも影響が出るため、共通処理の位置が本当に親クラスでよいかを確認する必要があると言えるでしょう。

この注意は、うるう年判定のように分岐条件が明確な処理にも通じますし、ここを基本と考えるとよいでしょう。条件式の組み立てを学びたい場合は、Javaでうるう年を判定のような題材でifreturnの関係を復習すると理解がつながります。

オーバーライドのカスタマイズ方法

カスタマイズでは、単に文言を変えるだけでなく、処理順を固定したり、子クラスに一部の処理を任せたりするのが基本です。こうした設計では、どのメソッドをfinalにし、どのメソッドをabstractにするかが読みやすさを左右します。

サンプルコード5:カスタムメソッドの作成

これを短い形で示すなら、親クラスのsoundを子クラスで変えるコードが分かりやすいです。サンプルコードは単純ですが、同じ考え方は通知方法、料金計算、表示形式の差し替えにも広げられますし、ここがポイントです。

class Animal {
    public void sound() {
        System.out.println('動物の声');
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println('ワンワン');
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();
    }
}

結果: 期待される出力は「ワンワン」です。DogAnimalsoundをオーバーライドしているため、犬用の処理が選ばれます。

そのカスタマイズを増やす場合、親クラス名とメソッド名が抽象的すぎると意図がぼやけます。processexecuteだけでは目的が読みにくいため、calculateFeeformatMessagevalidateInputのように役割が分かる名前にすると保守しやすいです。

サンプルコード6:動的メソッドディスパッチの利用

動的メソッドディスパッチは、コンパイル時の変数型ではなく、実行時の実体型に基づいてメソッドを選ぶ仕組みです。これはオーバーライドを使ったプログラム作成で頻出するため、Javaプログラミング学習の中盤で必ず押さえたい概念だと言えますが、これは押さえたい点です。

class Vehicle {
    void run() {
        System.out.println('車両が走行しています');
    }
}

class Car extends Vehicle {
    @Override
    void run() {
        System.out.println('車が走行しています');
    }
}

class Bike extends Vehicle {
    @Override
    void run() {
        System.out.println('バイクが走行しています');
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle[] vehicles = {new Vehicle(), new Car(), new Bike()};

        for (Vehicle vehicle : vehicles) {
            vehicle.run();
        }
    }
}

結果: 期待される出力は「車両が走行しています」「車が走行しています」「バイクが走行しています」の順です。配列内の宣言型はVehicleでも、実体がCarならCar.runが呼ばれます。

この形に慣れると、ifswitchで型を見分ける処理を減らせます。型ごとの差分を各クラスへ閉じ込めることで、呼び出し側のコードが短くなり、変更範囲も追いやすくなるのが目安です。

サンプルコード7:デザインパターンで使うオーバーライド

Template Methodパターンでは、親クラスが処理の大枠を決め、細部を子クラスのオーバーライドへ任せますが、これは押さえたい点です。処理順を固定したい場合は、流れを表すメソッドにfinalを付けると、子クラスによる順序変更を防げます。

abstract class Beverage {
    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    private void boilWater() {
        System.out.println('お湯を沸かします');
    }

    abstract void brew();

    private void pourInCup() {
        System.out.println('カップに注ぎます');
    }

    abstract void addCondiments();
}

class Tea extends Beverage {
    @Override
    void brew() {
        System.out.println('ティーバッグを浸します');
    }

    @Override
    void addCondiments() {
        System.out.println('レモンを追加します');
    }
}

class Coffee extends Beverage {
    @Override
    void brew() {
        System.out.println('コーヒーを淹れます');
    }

    @Override
    void addCondiments() {
        System.out.println('砂糖とミルクを追加します');
    }
}

public class Main {
    public static void main(String[] args) {
        Beverage tea = new Tea();
        tea.prepareRecipe();

        Beverage coffee = new Coffee();
        coffee.prepareRecipe();
    }
}

結果: 期待される出力は、紅茶では「お湯を沸かします」「ティーバッグを浸します」「カップに注ぎます」「レモンを追加します」、コーヒーでは「お湯を沸かします」「コーヒーを淹れます」「カップに注ぎます」「砂糖とミルクを追加します」です。

この例では、prepareRecipeが処理順を固定し、brewaddCondimentsだけを子クラスへ任せています。エスケープ処理や文字列整形のような共通処理でも似た発想が使えるため、文字列の扱いはJavaエスケープ処理の10ステップマスターガイドと合わせて学ぶとよいでしょう。

ℹ️ 補足: Template Methodパターンは継承を前提にします。差し替え対象が多く、実行時に組み合わせを変えたい場合は、継承より委譲やインターフェイスを使う設計も候補になるのがポイントです。

まとめ

Javaのオーバーライドは、親クラスのメソッドを子クラスで再定義し、同じ呼び出し方のまま実体に応じた処理へ切り替える仕組みです。プログラミング初心者にとっては、@Overrideextendssuperabstractをひとまとまりで覚えると、サンプルコードの意図を読み取りやすくなると整理できます。

その理解が進むと、プログラム作成では重複を減らすだけでなく、変更点を子クラスへ閉じ込める設計ができます。コーディング技法としてのオーバーライドは、単なる書き換えではなく、呼び出し側のコードを安定させるための設計手段です。

ただし、継承を深くしすぎると、親の変更が子へ広く影響するのが一般的です。interfaceabstract class、委譲、通常のメソッド分割を比較しながら、最も読みやすい形を選ぶのが現実的です。

Javaプログラミング学習では、オブジェクト指向の全体像も合わせて押さえると効果があります。関連する基礎を広げる場合は、オブジェクト指向を10ステップで完全マスターでクラス設計、継承、インターフェイスの関係を復習できると理解できます。

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。

関連記事