この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:7
ページ更新者:guest
更新日時:2026-06-11 07:10:02

タイトル: 継承の概念と必要性
SEOタイトル: OOP 継承完全ガイド — extends・super・Composition

この記事の要点
  • 継承 (Inheritance) = 既存クラスのフィールドとメソッドを引き継いで新クラスを作る仕組み
  • Java は 単一継承 (extends は 1 つ)、インターフェースは複数 implements 可
  • IS-A 関係が成り立つときだけ継承する (「Dog は Animal」○、「Stack は ArrayList」×)
  • Liskov 置換原則: 子は親の代わりに使えること
  • Composition over Inheritance: 多くの場合継承より「持つ」(has-a) 方が柔軟

継承の基本

// 親クラス (スーパークラス / 基底クラス)
public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public void sleep() {
        System.out.println(name + " is sleeping");
    }
}

// 子クラス (サブクラス / 派生クラス)
public class Dog extends Animal {

    public Dog(String name) {
        super(name);   // ★ 親のコンストラクタを呼び出し
    }

    // 親のメソッドをオーバーライド
    @Override
    public void sleep() {
        System.out.println(name + " (dog) is sleeping like a log");
    }

    // 子独自のメソッド
    public void bark() {
        System.out.println(name + " says: Woof!");
    }
}

// 利用
Dog d = new Dog("Pochi");
d.sleep();  // オーバーライドされた版
d.bark();   // 子独自

// 親型変数に子を入れる (ポリモーフィズム)
Animal a = new Dog("Hachi");
a.sleep();  // 動的ディスパッチで Dog の sleep が呼ばれる
// a.bark();  // ★ コンパイルエラー (Animal 型では bark を知らない)

extends と super

キーワード意味
extends子クラスが親を継承することを宣言
super(...)親のコンストラクタ呼び出し (コンストラクタ最初の文)
super.method()親のメソッドを呼び出し
super.field親のフィールドを参照
@Overrideオーバーライド宣言 (任意だが付けるべき)
final class継承禁止
final methodオーバーライド禁止

Constructor Chaining (親 → 子 の順で実行)

class A {
    public A() { System.out.println("A()"); }
}

class B extends A {
    public B() {
        // super(); ← ★ 暗黙で挿入される (引数なしコンストラクタ呼び出し)
        System.out.println("B()");
    }
}

class C extends B {
    public C() {
        super();   // 明示
        System.out.println("C()");
    }
}

new C();
// 出力:
// A()
// B()
// C()

// 親に引数なしコンストラクタが無いと
class X {
    public X(int n) { }
}
class Y extends X {
    public Y() {
        // ★ コンパイルエラー (X の引数なしコンストラクタが無い)
        // → 明示的に super(0); 等が必要
    }
}

IS-A 関係 (継承の判断基準)

「子は親の一種である」が言えるときだけ継承を使います:

  • Dog Animal ✅
  • Manager Employee ✅
  • Square Rectangle … ✗ (有名な反例: 高さと幅を独立変更できない)
  • Stack ArrayList … ✗ (Stack に get(0) が露出するのは設計ミス → Composition 推奨)

Liskov 置換原則 (LSP)

子は親の代わりに使えなければならない。つまり、親型変数に子インスタンスを入れても期待通り動くこと:

// 古典的反例 (Square extends Rectangle)
class Rectangle {
    protected int w, h;
    public void setWidth(int w)  { this.w = w; }
    public void setHeight(int h) { this.h = h; }
    public int area() { return w * h; }
}

class Square extends Rectangle {
    // 正方形なので幅と高さを連動
    @Override
    public void setWidth(int w)  { this.w = w; this.h = w; }
    @Override
    public void setHeight(int h) { this.w = h; this.h = h; }
}

void test(Rectangle r) {
    r.setWidth(5);
    r.setHeight(3);
    assert r.area() == 15;   // ★ Square だと 9 になり契約違反
}
// → Square IS NOT a Rectangle (LSP 違反)

ダイヤモンド継承問題と Java の解決策

C++ のような多重継承では「同じ親を 2 つ持つ子」で同じメソッドのどちらが呼ばれるか曖昧になります (ダイヤモンド問題)。Java は class の多重継承を禁止し、代わりに interface (および default メソッド) を用意:

interface Flyable {
    default void move() { System.out.println("Flying"); }
}
interface Swimmable {
    default void move() { System.out.println("Swimming"); }
}

class Duck implements Flyable, Swimmable {
    // ★ どちらの default を使うか曖昧 → コンパイルエラー
    @Override
    public void move() {
        Flyable.super.move();  // 明示で解決
    }
}

protected アクセス

public class Animal {
    protected String name;    // 子からアクセス可
    private int secretId;     // 子からも見えない

    protected void onSleep() { }   // フック (子が拡張用)
}

public class Dog extends Animal {
    public void greet() {
        System.out.println(name);    // ✅ protected フィールドにアクセス
        // System.out.println(secretId); // ★ NG (private)
    }
}

final class で継承禁止

// String, Integer, LocalDate などは final
public final class Coordinate {
    private final double x, y;
    // ...
}

// public class GeoCoordinate extends Coordinate { } // ★ コンパイルエラー

// 用途:
// - イミュータブル (不変) を保証したい
// - セキュリティ上、サブクラスで挙動を変えられたくない
// - 設計者がサブクラス対応していない

Composition over Inheritance (★ 重要原則)

「is-a」ではなく「has-a」が成り立つなら、継承ではなく Composition (持つ) を使う方が柔軟

// ❌ 継承
public class CountingList<E> extends ArrayList<E> {
    private int addCount = 0;

    @Override
    public boolean add(E e) {
        addCount++;
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
        // ★ ArrayList.addAll が内部で add を呼ぶ実装だと
        //   addCount が 2 倍カウントされる (実装に依存するバグ)
    }
}

// ✅ Composition
public class CountingList<E> {
    private final List<E> inner = new ArrayList<>();
    private int addCount = 0;

    public boolean add(E e) {
        addCount++;
        return inner.add(e);
    }

    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return inner.addAll(c);
    }
}

言語別比較

言語継承記法多重継承
Javaclass B extends A不可 (interface 複数 OK)
PHPclass B extends A不可 (trait あり)
C#class B : A不可 (interface 複数 OK)
Pythonclass B(A):★ 可 (MRO で解決)
C++class B : public A★ 可 (virtual 継承)
Rubyclass B < A不可 (Mixin で実質可)

Mixin / trait

多重継承の代わりに、PHP の trait や Ruby / Python の Mixin で「振る舞いを横断的に追加」する仕組み:

trait Loggable {
    public function log(string $msg): void {
        error_log(static::class . ": $msg");
    }
}

class UserService {
    use Loggable;   // trait を取り込む
}

(new UserService())->log("created");

FAQ

Q: 継承と Composition どちらを使うべき?
A: 迷ったら Composition。IS-A が自然に成り立つ場合のみ継承。

Q: extendsimplements の違い
A: extends はクラス継承 (1 つだけ)、implements はインターフェース実装 (複数可)。クラスは extends X implements Y, Z のように両方使える。

Q: 親クラスを変更すると子に影響が出る
A: 継承の宿命 (壊れやすい基底クラス問題)。public API を慎重に設計し、安易な protected フィールド公開は避ける。