タイトル: 継承の構文
SEOタイトル: Java 継承の構文完全ガイド(extends / super / @Override / abstract / sealed)
| この記事の要点 |
|
extends による継承
Java では extends で親クラスを 1 つだけ継承します。すべてのクラスは暗黙のうちに java.lang.Object を継承しているため、extends Object は省略可能です。
// 親クラス
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void speak() {
System.out.println(name + " makes a sound");
}
}
// 子クラス
public class Dog extends Animal {
public Dog(String name) {
super(name); // 親コンストラクタを呼ぶ (省略すると引数なしを暗黙呼び出し)
}
@Override
public void speak() {
System.out.println(name + " says Woof");
}
}
// 利用
Animal a = new Dog("Pochi");
a.speak(); // Pochi says Woof ← 動的束縛 (ポリモーフィズム)
super キーワード
| 用法 | 説明 |
|---|---|
super(...) | 親コンストラクタ呼び出し。子コンストラクタの最初の文で 1 回だけ |
super.method() | 親のメソッドを呼ぶ (オーバーライド後でも親の実装にアクセス) |
super.field | 親のフィールド参照 (同名で隠蔽されている場合) |
public class Cat extends Animal {
private int lives;
public Cat(String name, int lives) {
super(name); // 親コンストラクタ
this.lives = lives;
}
@Override
public void speak() {
super.speak(); // 親の speak() も呼ぶ
System.out.println(name + " says Meow");
}
}
@Override アノテーション
オーバーライド時は必ず @Override を付けます。シグネチャを間違えるとコンパイルエラーになり、タイポによる「オーバーライドしたつもり」事故を防げます。
public class Bird extends Animal {
// ❌ シグネチャ間違い: 親には speak() しかない
@Override
public void speek() { // typo!
// → error: method does not override or implement a method from a supertype
}
// ✅ 正しいオーバーライド
@Override
public void speak() {
System.out.println(name + " sings");
}
}
final と abstract
// final class: これ以上継承できない
public final class String { ... } // 標準ライブラリの String も final
// final method: オーバーライド禁止
public class Service {
public final void log(String msg) { ... } // 子で上書きできない
}
// abstract class: インスタンス化できない、子で実装が必要
public abstract class Shape {
public abstract double area(); // 実装なし。子で必須実装
public void print() { // 通常メソッドも書ける
System.out.println("area=" + area());
}
}
public class Circle extends Shape {
private double r;
public Circle(double r) { this.r = r; }
@Override
public double area() { return Math.PI * r * r; }
}
new Shape(); // ❌ error: Shape is abstract
new Circle(5); // ✅ OK
interface による多重継承の代替
Java はクラスの多重継承を禁止。代わりに interface を複数 implements できます。
public interface Flyable {
void fly();
default void glide() { // Java 8+ default メソッド
System.out.println("gliding");
}
}
public interface Swimmable {
void swim();
}
// 複数 interface を実装可能
public class Duck extends Animal implements Flyable, Swimmable {
public Duck(String n) { super(n); }
@Override public void fly() { System.out.println(name + " flies"); }
@Override public void swim() { System.out.println(name + " swims"); }
}
Diamond Problem
同名 default メソッドを持つ 2 つの interface を実装すると衝突するため、明示的に解決が必要:
interface A { default void hello() { System.out.println("A"); } }
interface B { default void hello() { System.out.println("B"); } }
class C implements A, B {
// どちらを採用するか明示しないとコンパイルエラー
@Override
public void hello() {
A.super.hello(); // A を採用
}
}
static メソッドはオーバーライドできない
class Parent {
public static void hi() { System.out.println("Parent.hi"); }
}
class Child extends Parent {
// これは hiding (隠蔽)、override ではない
public static void hi() { System.out.println("Child.hi"); }
}
Parent p = new Child();
p.hi(); // → "Parent.hi" (型でディスパッチされる)
Java 17+ : sealed class
継承を一部のクラスにだけ許可する機構。代数的データ型 (sum type) を表現できます。
public sealed class Shape
permits Circle, Rectangle, Triangle { }
public final class Circle extends Shape { ... }
public final class Rectangle extends Shape { ... }
public non-sealed class Triangle extends Shape { ... } // ここから先は自由継承
// switch パターン (Java 21)
String name = switch (shape) {
case Circle c -> "circle";
case Rectangle r -> "rect";
case Triangle t -> "tri";
}; // sealed なので default 不要 (網羅チェック)
コンストラクタチェーン
class A {
A() { System.out.println("A()"); }
}
class B extends A {
B() {
// super(); が暗黙で呼ばれる
System.out.println("B()");
}
}
class C extends B {
C() {
System.out.println("C()");
}
}
new C();
// 出力:
// A()
// B()
// C()
FAQ
Q: なぜ Java は多重継承を禁止?
A: Diamond Problem の曖昧さを避けるため。interface で代替できるし、Java 8 default メソッドで実装も持てるので実用上問題ない。
Q: protected と package-private の違いは?
A: protected は同パッケージ + サブクラス、package-private (修飾子なし) は同パッケージのみ。継承先からアクセスさせたいなら protected。
Q: 継承より委譲 (composition) を使うべき?
A: 一般論として yes。Effective Java 「継承よりコンポジション」。継承は親実装の変更が子に波及する脆さがある。