何が「オーバーライド」か
メソッドのオーバーライドは、親クラスが持つメソッドの「振る舞いを、子クラスで置き換える」ことです。継承関係にあるとき、同じメソッド名・同じ引数(シグネチャ)を子クラスで再定義すると、実行時には「実体の型」に応じたメソッドが選ばれます。これが多態性(ポリモーフィズム)で、拡張先ごとに振る舞いを差し替える設計が簡潔になります。
基本ルールと @Override
必須の一致と推奨アノテーション
オーバーライドは「メソッド名・引数の並び・戻り値の互換性」が親と合致している必要があります。Java では @Override を付けておくと、間違って「別メソッド(オーバーロード)」になってしまう事故をコンパイラが防いでくれます。
abstract class Shape {
abstract double area();
}
final class Rect extends Shape {
@Override double area() { return w * h; } // 正しいオーバーライド
Rect(double w, double h) { this.w = w; this.h = h; }
private final double w, h;
}
final class Circle extends Shape {
@Override double area() { return Math.PI * r * r; }
Circle(double r) { this.r = r; }
private final double r;
}
Java@Deprecated な親メソッドを置き換える、インターフェースの実装を確実にしたい、という場面でも @Override は強力なガードになります。
動的ディスパッチとポリモーフィズム(重要ポイントの深掘り)
参照型ではなく「実体の型」で決まる
メソッド呼び出しは、変数の参照型ではなく、その時持っている実体の型で決まります。これにより「共通の型(親)で扱い、具体型に応じて振る舞いが切り替わる」柔軟な設計が可能です。
Shape s = new Rect(3, 4);
System.out.println(s.area()); // Rect の実装が呼ばれる
s = new Circle(2);
System.out.println(s.area()); // Circle の実装に切り替わる
Java静的メソッドやフィールド参照は動的ディスパッチに参加しないため、オーバーライドの対象にはなりません。インスタンスメソッドが多態性の中心です。
戻り値・アクセス修飾子・例外の互換性
戻り値は「共変戻り値」が許される
戻り値は親より「より具体的な型(サブタイプ)」に狭めることができます。これを共変戻り値と呼び、使い勝手が向上します。
class Animal { }
class Cat extends Animal { }
class Factory {
Animal create() { return new Animal(); }
}
class CatFactory extends Factory {
@Override Cat create() { return new Cat(); } // 共変戻り値(OK)
}
Javaアクセス修飾子は「広げるのはOK、狭めるのはNG」
親が public のメソッドを子で protected や private に狭めることはできません。公開契約を壊すためです。逆に、package-private を public に広げることは可能です。
class Base { void greet() {} } // package-private
class Sub extends Base {
@Override public void greet() {} // より広い公開(OK)
}
Java例外は「より少なく・より具体的」に
throws のチェック例外は、親の宣言より「狭い例外」を投げるのは許されますが、広げることはできません。非チェック例外(RuntimeException系)は宣言外でも投げられますが、契約の明確化を心がけましょう。
オーバーライドできないものと super
final・static・private は対象外
final メソッドは上書き不可。static はクラスメソッドであり、オーバーライドではなく「隠蔽(hiding)」になって動的には切り替わりません。private はクラス外から見えないため、そもそもオーバーライド不能です。
class Base {
final void lock() {} // 上書き不可
static void util() {} // 隠蔽はできるが動的切替なし
private void internal() {} // 子から見えない
}
Javasuper の使いどころ
親の処理を拡張する場合、子のメソッド内から super を呼んで「前後や途中に追加処理」を差し込めます。テンプレートメソッドやフックの実装で有用です。
class Base {
void render() { System.out.println("header"); }
}
class Sub extends Base {
@Override void render() {
super.render(); // 親の前処理
System.out.println("body"); // 追加
}
}
Javaオーバーロードとの違いと混同防止(重要ポイントの深掘り)
引数が違えば「別メソッド(オーバーロード)」
オーバーライドは「親と同じシグネチャ」を置き換えます。引数の型や個数が違うと、それはオーバーロード(同名別メソッド)です。@Override を付けていれば、意図せずオーバーロードしてしまうミスを防げます。
class Base { void send(String s) {} }
class Sub extends Base {
// @Override を付けるとコンパイル時に誤りを検出できる
void send(Object o) {} // これはオーバーロード。オーバーライドではない
}
Java設計パターンと実践例
テンプレートメソッドで差し替えポイントを作る
共通の処理枠組みは親で final にし、差し替えたい場所だけ protected/abstract にして子クラスでオーバーライドします。拡張性と安全性のバランスが良い定番です。
abstract class Importer {
public final void run(String path) {
var data = load(path);
var normalized = normalize(data);
save(normalized);
}
protected abstract String load(String path);
protected String normalize(String s) { return s == null ? "" : s.trim(); }
protected abstract void save(String data);
}
final class CsvImporter extends Importer {
@Override protected String load(String path) { return "a,b,c"; }
@Override protected void save(String data) { System.out.println("save:" + data); }
}
Javaequals/hashCode/toString のオーバーライドと同様に「契約」を守る
オーバーライドは「外部が期待する契約」を壊さないことが大前提です。引数の前提、戻り値の意味、例外方針は親と互換にし、必要な拡張はドキュメントとテストで明確化します。
つまずきやすいポイントと回避
オーバーライドしたつもりが引数違いでオーバーロードになり、実行時に親のメソッドが呼ばれてしまう混乱が典型です。必ず @Override を付けて防ぎます。親の公開範囲より狭くする、チェック例外を広げる、といった互換性違反はコンパイルエラーや破壊的変更につながります。戻り値の共変は便利ですが、「より広い型」に戻してしまうと互換性を逸脱するため注意してください。final/static/private はそもそもオーバーライド対象外で、動的切り替えが期待できません。
例題で理解を固める
abstract class Notifier {
public final void sendAll(java.util.List<String> targets) {
for (var t : targets) sendTo(t);
}
protected abstract void sendTo(String target); // 差し替えポイント
}
final class EmailNotifier extends Notifier {
@Override protected void sendTo(String target) {
System.out.println("Email -> " + target);
}
}
final class SmsNotifier extends Notifier {
@Override protected void sendTo(String target) {
System.out.println("SMS -> " + target);
}
}
Notifier n = new EmailNotifier();
n.sendAll(java.util.List.of("taro@example.com", "hanako@example.com"));
// 実体に応じて sendTo が切り替わる(動的ディスパッチ)
Java仕上げのアドバイス(重要部分のまとめ)
オーバーライドは「親の契約を守りつつ、子で振る舞いを差し替える」技術です。同じシグネチャで再定義し、@Override で意図を固定。戻り値は共変で狭められ、アクセスは広げられるが、例外は広げない。final/static/private は対象外で、動的ディスパッチはインスタンスメソッドだけ。テンプレートメソッドで差し替え点を設計すると、拡張しやすく壊れにくいコードになります。
