当サイトのリンクには広告が含まれています。

Javaオーバーライドとは?書き方・5つのルール・実務活用まで解説

当ページのリンクには広告が含まれています。
Mirai

Javaを学習しているんだけど、オーバーライドって何?

Zetto

「親から引き継いだ処理を子クラスで上書きする仕組み」で、クラスごとに動作を柔軟にカスタマイズできるんだ。

この記事では、Javaのオーバーライドの基本から書き方・5つのルール・実務での活用まで解説します。

本記事の専門性
現役エンジニアのZettoです。Java SilverとJava Goldの資格を持ち、JavaとSpring Bootを使ったフリーランス案件を複数経験しています。

この記事を読めば、オーバーライドの書き方・守るべき5つのルールまで、ひと通り理解できます。

ぜひ参考にしてみてください。

目次

Javaのオーバーライドとは何か

オーバーライドと継承の基本

オーバーライドを一言で言うと

オーバーライド(override)とは、親クラス(スーパークラス)で定義されたメソッドを、子クラス(サブクラス)で同じメソッド名・引数・戻り値で上書き定義することです。

Javaのオーバーライドとは

「上書き」という字の通り、子クラスが親クラスの処理を自分用に書き換えるイメージですね。

// 親クラス
class Animal {
    void sound() {
        System.out.println("動物が鳴く");
    }
}

// 子クラスでオーバーライド
class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("ワンワン");
    }
}

この例では、Animalクラスのsound()メソッドを、Dogクラスが「ワンワン」という独自の処理に上書きしています。

@Overrideアノテーションは必須ではありませんが、つけることを強くおすすめします。理由はあとで解説しますね。

オーバーライドが必要な理由

親クラスに汎用的な処理を書いて、子クラスごとに動作を変えたい場面は、実務でも頻繁に出てきます。

オーバーライドを使う主なメリットは以下のとおりです。

  • コードの重複を減らせる
  • 親クラスの設計を維持しつつ、子クラスで柔軟に振る舞いを変えられる
  • ポリモーフィズム(多態性)を実現できる

例えば、僕がJava案件で携わった在庫管理システムでも、共通処理という親クラスがあって、それぞれの子クラスがオーバーライドで処理を切り分けていました。

@Overrideアノテーションをつける理由

@Overrideは省略可能ですが、必ずつける習慣をつけておくべきです

理由はシンプルで、コンパイル時にミスを検出してくれるからですね。

class Dog extends Animal {
    @Override
    void sond() { // タイポがあるとコンパイルエラーになる
        System.out.println("ワンワン");
    }
}

@Overrideがなければ、スペルミスしても新しいメソッドとして定義されるだけで、エラーになりません。意図せずオーバーライドできていない、という事態が起きやすくなります。

実務では@Overrideなしのコードはレビューで指摘されることが多いので、最初からつける癖をつけておくといいですね。

オーバーライドの3つのルール

オーバーライドには守るべきルールがあります。これを間違えるとコンパイルエラーになるので、しっかり押さえておきましょう。

  • メソッド名・引数・戻り値の型を親クラスと一致させる
  • アクセス修飾子は親クラスと同じか、より広い範囲にするprotectedpublicはOK。publicprivateはNG)
  • finalstaticprivateなメソッドはオーバーライドできない

特に2つ目のアクセス修飾子のルールは見落としやすいポイントです。

Zetto

親がprotectedなのに子でprivateにしようとするとエラーになるので、注意してください。

オーバーライドの書き方と覚えておきたい5つのルール

オーバーライド5つのルール

ここではオーバーライドの具体的な書き方と、守るべき5つのルールを解説します。

  • 基本的な書き方とコード例
  • @Overrideアノテーションをつける理由
  • アクセス修飾子は「同じか緩く」する
  • static・private・finalメソッドはオーバーライドできない
  • 戻り値の型と共変戻り値型(コバリアント)
  • superキーワードで親クラスのメソッドを呼び出す

順番に見ていきましょう。

基本的な書き方とコード例

オーバーライドの基本は、親クラスと同じメソッドシグネチャ(名前・引数の型・数)で、子クラスに再定義することです。

// 親クラス
public class Animal {
    public void sound() {
        System.out.println("なんらかの鳴き声");
    }
}

// 子クラス(オーバーライドあり)
public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("ワン!");
    }
}

// 実行例
Animal dog = new Dog();
dog.sound(); // 出力:ワン!

Animal 型の変数 dogDog のインスタンスを代入しても、実際に動くのは Dogsound() です。これがオーバーライドの本質で、型と実際の処理が切り離される動きをします。

@Overrideアノテーションをつける理由

@Override はつけなくてもオーバーライド自体は動きます。でも、必ずつける習慣にすることを強くおすすめします。

理由は2つあります。

  • タイポ(打ちミス)をコンパイルエラーで検出できる@Override があると、シグネチャが親クラスと一致しない場合にコンパイルエラーになる。なければ「新しいメソッドを追加しただけ」と解釈されて、サイレントバグ(動いているように見えて実は意図通りでないバグ)になる
  • コードの意図が伝わる:「このメソッドは意図的に親クラスを上書きしている」と読み手に伝えられる

僕自身、現場でこのミスを何度か目にしました。@Override なしでメソッド名を1文字ミスしても、コードは普通に動いてしまう。そのままテストを通過して、後からバグとして発覚するんですよね。

// @Overrideなしで誤ったメソッド名を書いた例
public class Dog extends Animal {
    public void Sound() { // 大文字S → 別メソッドとして扱われる(オーバーライドされない!)
        System.out.println("ワン!");
    }
}

@Override をつけていれば、上のコードはコンパイルエラーになります。即座に気づけます。

アクセス修飾子は「同じか緩く」する

オーバーライドするとき、アクセス修飾子(publicprotectedprivate など)にルールがあります。

子クラスのアクセス修飾子は、親クラスと同じか、より緩くする必要があります。

緩さの順序は次の通りです。

private < デフォルト(パッケージ内のみ) < protected < public

つまり、親クラスで protected のメソッドは、子クラスで protectedpublic にはできますが、private にはできません。

// OK:protectedからpublicへ(緩くしている)
public class Animal {
    protected void sound() { ... }
}
public class Dog extends Animal {
    @Override
    public void sound() { ... }  // OK
}

// NG:protectedからprivateへ(厳しくしている)→ コンパイルエラー
public class Cat extends Animal {
    @Override
    private void sound() { ... }  // コンパイルエラー
}

static・private・finalメソッドはオーバーライドできない

オーバーライドできないメソッドが3種類あります。

  • private メソッド:子クラスからそもそも見えないため、オーバーライド不可
  • final メソッド:「これ以上変更不可」の宣言なので、意図的にオーバーライドを禁止したいときに使う
  • static メソッド:インスタンスではなくクラスに紐づくため、オーバーライドではなく「メソッドの隠蔽(hiding)」になる

static は少し注意が必要です。子クラスで同名の static メソッドを定義することはできますが、これはオーバーライドではありません。

ポリモーフィズムが効かないので、動作が変わります。「できるけど、オーバーライドとは別物」と覚えておくと混乱しにくいかなと思います。

戻り値の型と共変戻り値型(コバリアント)

オーバーライドするとき、戻り値の型は原則として親クラスと同じにします。

ただし、Java 5以降では共変戻り値型(コバリアント:covariant return type)が使えます。戻り値の型を「親クラスのサブクラス」に変更できる仕組みです。

public class Animal {
    public Animal create() {
        return new Animal();
    }
}

public class Dog extends Animal {
    @Override
    public Dog create() {  // AnimalのサブクラスであるDogを戻り値にできる
        return new Dog();
    }
}

頻繁に使うものではありませんが、ライブラリのコードを読むときに出てくることがあります。「戻り値の型をサブクラスに変えられる場合がある」と知っておくと、迷わなくなります。

superキーワードで親クラスのメソッドを呼び出す

オーバーライドすると、子クラスでは親クラスのメソッドが上書きされます。ただ、「親の処理に追加する」形で実装したいケースも多いです。

そのときに使うのが super キーワードです。

public class Animal {
    public void sound() {
        System.out.println("動物が鳴いています");
    }
}

public class Dog extends Animal {
    @Override
    public void sound() {
        super.sound();               // 親のsound()を先に呼ぶ
        System.out.println("ワン!"); // その後に子クラスの処理を追加
    }
}

// 実行結果:
// 動物が鳴いています
// ワン!

実務では、Spring Bootのフィルター処理やコンストラクタで super を使うことが多いです。

Zetto

「共通処理を親に持たせて、追加処理だけ子に書く」という設計パターンでよく登場しますね。

オーバーライドとオーバーロードの違い

オーバーライドとオーバーロード

Java学習でよく混乱するのが、オーバーライドとオーバーロードの違いです。

  • 定義の違いをコードで確認
  • 混同しやすいポイントと見分け方

順に確認しましょう。

定義の違いをコードで確認

まず、2つの定義を整理します。

  • オーバーライド(override):親クラスのメソッドを子クラスで再定義すること。継承関係が必要
  • オーバーロード(overload):同じクラス内で、引数が異なる同名のメソッドを複数定義すること

コードで比較するとわかりやすいです。

// ① オーバーライドの例(継承関係あり・シグネチャ同じ)
public class Animal {
    public void sound() {
        System.out.println("なんらかの鳴き声");
    }
}
public class Dog extends Animal {
    @Override
    public void sound() {       // 同じシグネチャで上書き → オーバーライド
        System.out.println("ワン!");
    }
}

// ② オーバーロードの例(同じクラス内・引数が違う)
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    public double add(double a, double b) { // 引数の型が違う → オーバーロード
        return a + b;
    }
}

混同しやすいポイントと見分け方

2つを見分けるポイントは「どのクラスにあるか」と「引数が同じかどうか」で判断できます。

オーバーライドオーバーロード
場所親クラスと子クラス(継承必須)同一クラス内
メソッド名同じ同じ
引数同じ異なる(型・数・順序)
戻り値同じ(共変戻り値型は例外)自由
@Overrideつけるつけない

覚え方として「オーバーライドは縦(継承方向)・オーバーロードは横(同クラス内)」というイメージが役立ちます。

迷ったときは、@Override がついているかどうかを確認するのがいちばん早い見分け方ですね。

Mirai

名前が似てるからずっと混乱してた!「縦か横か」で考えるとスッキリするね。

Zetto

そうそう!名前が紛らわしいんだよね。でも「継承が必要かどうか」と「@Overrideがつくかどうか」で確実に見分けられるよ。

ポリモーフィズムと実務でのオーバーライド活用

ポリモーフィズムとオーバーライド

オーバーライドの書き方が身についたら、実務でどう使われるかを見ていきましょう。

  • ポリモーフィズム(多態性)とオーバーライドの関係
  • equals・hashCodeをオーバーライドする理由
  • Spring Bootでオーバーライドが使われる場面
  • インターフェースのdefaultメソッドとオーバーライド(Java 8以降)

1つずつ解説していきます。

ポリモーフィズム(多態性)とオーバーライドの関係

ポリモーフィズム(多態性)とは、「同じ型でも、実際のオブジェクトの種類によって異なる動作をする仕組み」です。

オーバーライドがあって初めて、ポリモーフィズムが成立します。

public class Animal {
    public void sound() { System.out.println("なんらかの鳴き声"); }
}
public class Dog extends Animal {
    @Override public void sound() { System.out.println("ワン!"); }
}
public class Cat extends Animal {
    @Override public void sound() { System.out.println("ニャー!"); }
}

// ポリモーフィズムを使った呼び出し
List animals = new ArrayList<>();
animals.add(new Dog());
animals.add(new Cat());

for (Animal animal : animals) {
    animal.sound(); // Dogならワン!、CatならニャーA→動物の種類によって自動で切り替わる
}
// 出力:
// ワン!
// ニャー!

Animal 型の変数で管理しているのに、実際のオブジェクトに合った処理が動く点がポイントです。

新しい動物クラスを追加したい場合は animals.add(new Bird()) を足すだけで済みます。既存コードを一切変える必要がないので、拡張性が高い設計になります。

equals・hashCodeをオーバーライドする理由

JavaのすべてのクラスはObjectクラスを継承しています。Object クラスには equals()hashCode() というメソッドがあり、実務ではよくオーバーライドします。

デフォルトの equals() はオブジェクトのアドレス(参照)で比較します。内容が同じでも別オブジェクトだと「異なる」と判断してしまいます。

// equalsをオーバーライドせずに内容比較しようとした場合
Person p1 = new Person("Taro", 25);
Person p2 = new Person("Taro", 25);
System.out.println(p1.equals(p2)); // false(アドレスが違うため)

// equalsをオーバーライドして内容で比較するようにすれば
System.out.println(p1.equals(p2)); // true(名前と年齢が同じなので)

また、HashSetHashMap を使うときは hashCode() もセットでオーバーライドするのが鉄則です。equals() で同じと判断されるオブジェクトは、hashCode() も同じ値を返さないとコレクションが正しく動作しないからです。

equals をオーバーライドしたら hashCode も必ずオーバーライドする」とセットで覚えておきましょう。

Spring Bootでオーバーライドが使われる場面

Spring Bootの案件でオーバーライドが登場するのは、主に2つの場面です。

カスタムフィルターの実装

HTTPリクエストに共通処理を挟みたいとき、OncePerRequestFilter を継承して doFilterInternal() をオーバーライドします。

public class AuthFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        // 認証チェックの処理を追加
        filterChain.doFilter(request, response);
    }
}

UserDetailsServiceのloadUserByUsernameをオーバーライド

Spring Securityで認証処理を自前で実装するとき、インターフェース UserDetailsService のメソッドをオーバーライドします。フレームワーク側が「このメソッドを呼ぶ」と決めていて、中身だけを自分で実装する形です。

僕がJavaのフリーランス案件で担当した医療機器販売関連のWebアプリでも、このパターンでカスタム認証処理を実装していました。フレームワークの「呼び出す側」と「実装する側」を切り離す設計は、まさにオーバーライドとポリモーフィズムの恩恵です。

インターフェースのdefaultメソッドとオーバーライド(Java 8以降)

Java 8から、インターフェースに default キーワードでメソッドの実装を持てるようになりました。

public interface Greetable {
    default void greet() {
        System.out.println("こんにちは!");
    }
}

public class FormalGreeter implements Greetable {
    @Override
    public void greet() {  // defaultメソッドをオーバーライドして挙動を変える
        System.out.println("はじめまして。どうぞよろしくお願いいたします。");
    }
}

インターフェースの default メソッドはオーバーライドしなくてもそのまま使えますが、クラスごとに動作を変えたいときにオーバーライドします。ライブラリやフレームワークが後方互換性を保ちながら新機能を追加するためにも活用されています。

オーバーライドは「書き方の話」だけでなく、設計の根幹にある仕組みです。ポリモーフィズム・equals/hashCode・フレームワーク連携まで、実務のあらゆる場面に登場します。

Zetto

Javaの案件では、毎日のようにオーバーライドが出てきます。書けるだけでなく「なぜここでオーバーライドしているのか」を説明できるレベルになると、コードレビューでも自信を持って発言できます。

Javaオーバーライドの演習問題

オーバーライド演習問題

理解を深めるために、演習問題を3つ用意しました。

演習問題1:動物クラスを継承してsoundメソッドをオーバーライドせよ

問題

以下の Animal クラスを継承した Cat クラスと Bird クラスを作成してください。それぞれの sound() メソッドをオーバーライドし、Cat は「ニャー!」、Bird は「チュンチュン!」と出力されるようにしてください。

main メソッドでは Animal 型のリストに CatBird を追加し、sound() を呼び出してください。

public class Animal {
    public void sound() {
        System.out.println("なんらかの鳴き声");
    }
}

解答例

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

public class Bird extends Animal {
    @Override
    public void sound() {
        System.out.println("チュンチュン!");
    }
}

// mainメソッド
public class Main {
    public static void main(String[] args) {
        List animals = new ArrayList<>();
        animals.add(new Cat());
        animals.add(new Bird());

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

// 出力:
// ニャー!
// チュンチュン!

CatBird はどちらも Animal 型のリストで管理できています。animal.sound() を呼ぶと、実際のオブジェクトに応じた処理が動きます。これがポリモーフィズムです。@Override をつけることで、意図通りにオーバーライドされているとコンパイラが保証してくれます。

演習問題2:@Overrideなしに潜むバグを見つけよ

問題

以下のコードを実行すると「ワン!」と出力されることを期待しています。ところが実際には「なんらかの鳴き声」と出力されてしまいます。バグを見つけて修正してください。

public class Animal {
    public void sound() {
        System.out.println("なんらかの鳴き声");
    }
}

public class Dog extends Animal {
    public void Sound() {  // ← ここに注目
        System.out.println("ワン!");
    }
}

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

解答例

public class Dog extends Animal {
    @Override
    public void sound() {  // 大文字Sを小文字sに修正 + @Overrideを追加
        System.out.println("ワン!");
    }
}

Sound()sound() とは別のメソッドとして認識されていたため、オーバーライドが起きていませんでした。@Override をつけていれば、コンパイル時にエラーになって即座に気づけます。このバグは実務でも起こりやすいパターンです。@Override を常につける習慣の重要性がわかる問題です。

演習問題3:ポリモーフィズムを使って処理を一本化せよ

問題

以下のコードは動きますが、動物の種類が増えるたびに if-else が増えて保守しにくくなります。ポリモーフィズムとオーバーライドを使ってリファクタリング(コードの整理・改善)してください。

public class Main {
    public static void main(String[] args) {
        String type = "dog";

        if (type.equals("dog")) {
            System.out.println("ワン!");
        } else if (type.equals("cat")) {
            System.out.println("ニャー!");
        } else if (type.equals("bird")) {
            System.out.println("チュンチュン!");
        }
    }
}

解答例

// 親クラス
public class Animal {
    public void sound() { }
}

// 各サブクラス
public class Dog extends Animal {
    @Override public void sound() { System.out.println("ワン!"); }
}
public class Cat extends Animal {
    @Override public void sound() { System.out.println("ニャー!"); }
}
public class Bird extends Animal {
    @Override public void sound() { System.out.println("チュンチュン!"); }
}

// mainメソッド(if-elseが不要になる)
public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 種類を切り替えたいときはここだけ変える
        animal.sound();
    }
}

オーバーライドとポリモーフィズムを使うと、if-else を削除できます。新しい動物を追加するときはサブクラスを1つ作るだけで済み、既存のコードには一切手を加えません。

これはオブジェクト指向設計の原則「開放閉鎖原則(OCP:Open/Closed Principle)」と呼ばれる考え方に通じています。実務でも意識するとコードの保守性が大きく上がります。

オーバーライドを理解すれば、Javaの設計力は一段上がる

Java設計力の向上

この記事では、Javaのオーバーライドについて基礎から実務まで解説しました。

重要ポイントを整理します。

  • 継承とセットで理解する:オーバーライドは継承関係がある子クラスで親メソッドを再定義する仕組み
  • @Overrideは必須の習慣:タイポによるサイレントバグを防ぐための最初の一手
  • 5つのルールを守る:シグネチャ一致・修飾子は緩く・static/private/finalは不可・superで親を呼べる
  • ポリモーフィズムと組み合わせると真価を発揮:拡張しやすく保守しやすい設計になる
  • 実務の頻出パターン:equals/hashCode・Spring Bootのカスタム処理・インターフェースのdefaultメソッド

オーバーライドは「メソッドを上書きする書き方」だけで終わらず、ポリモーフィズムと組み合わせることで設計の柔軟さが変わります。

Mirai

オーバーライドって書き方だけじゃなくて、設計の話だったんだね。演習問題で手を動かしたら一気にクリアになった!

Zetto

「書ける」から「設計で使える」に変わると、コードの見え方がまったく変わるからね。次はインターフェースと抽象クラスにも挑戦してみて。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次