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

タイトル: 継承
SEOタイトル: OOP 継承 (Inheritance) 完全ガイド

この記事の要点
  • 継承 (Inheritance): 既存クラスを土台に新しいクラスを派生させる仕組み。「is-a」関係
  • Single Inheritance (Java/C#/PHP): 親 1 つ + interface 多重実装。Multiple Inheritance (C++/Python): 親複数可
  • Diamond Problem: 多重継承で共通祖先のメソッドが衝突。C3 線形化 / virtual 継承で解決
  • virtual function: サブクラスで上書き可能なメソッド。Java/C# は既定で virtual
  • SOLID の L: Liskov Substitution Principle — 親型の変数にサブクラスを入れ替えても動くべき
  • Composition Over Inheritance: 継承よりコンポジション (has-a) を優先する設計原則
  • Sealed Class (Java 17+/C#/Kotlin): 継承可能な子クラスを限定 → 網羅性チェック
  • Fragile Base Class 問題: 親変更がサブクラス全体に波及するリスク

継承とは

継承は「is-a」関係を表す OOP の基本機構。「犬は動物の一種」「正方形は四角形の一種」のような階層を、コードで表現します。 親クラスのフィールド/メソッドを引き継ぎ、サブクラスで追加・上書きできます。

// Java での継承の基本
class Animal {
    protected String name;
    public Animal(String name) { this.name = name; }
    public void eat() { System.out.println(name + " eats."); }
    public void sound() { System.out.println("some sound"); }
}

class Dog extends Animal {
    public Dog(String name) { super(name); }   // 親コンストラクタ呼出

    @Override
    public void sound() {                       // メソッド上書き
        System.out.println(name + ": ワン");
    }

    public void fetch() {                       // 新規メソッド
        System.out.println(name + " runs after the ball");
    }
}

Dog pochi = new Dog("ポチ");
pochi.eat();    // 親のメソッド利用可
pochi.sound();  // サブクラスの実装
pochi.fetch();  // サブクラス独自

// is-a 関係: Dog は Animal として扱える
Animal a = pochi;   // OK (アップキャスト暗黙)
a.sound();          // "ポチ: ワン" (多態性、virtual dispatch)

各言語での継承構文

言語構文多重継承
Javaclass A extends B implements I1, I2不可 (interface のみ多重)
C#class A : B, I1, I2不可 (interface のみ多重)
C++class A : public B, public C可 (virtual 継承で diamond 解決)
Pythonclass A(B, C):可 (C3 線形化)
PHPclass A extends B implements I1不可 (trait で部分的に補完)
Kotlinclass A : B(), I1不可 (interface のみ多重)
Rubyclass A < B不可 (Module の mixin)
Scalaclass A extends B with T1 with T2trait で複数可

Diamond Problem (多重継承の罠)

下図のように 2 つの親が共通祖先を持つと、同名メソッドが衝突します:

    Animal
    /    \
 Bird    Mammal
    \    /
   Platypus  ← どっちの walk() を継承?

各言語の解決策

言語解決
C++virtual 継承 + 明示的なスコープ解決 Bird::walk()
PythonC3 線形化 (MRO: Method Resolution Order)。super().__mro__ で確認
Java / C#そもそも多重継承不可。interface だけなら default メソッド衝突時に override 強制
PHPtrait の insteadof で明示的選択
# Python: C3 線形化
class A:
    def hello(self): print("A")
class B(A):
    def hello(self): print("B")
class C(A):
    def hello(self): print("C")
class D(B, C):
    pass

D().hello()       # B (MRO: D → B → C → A → object)
print(D.__mro__)

Composition Over Inheritance

近年の設計原則:継承より組み合わせ (Composition) を優先する。理由:

  • 柔軟性: 実行時に挙動を入れ替えられる (Strategy パターン)
  • Fragile Base Class 問題を回避 (親変更がサブクラス全部に波及)
  • 多重継承の複雑さを避けられる
  • テストしやすい (Mock しやすい)
// ❌ 継承で機能拡張 (Duck が Quack を直接実装)
abstract class Duck {
    abstract void quack();
    abstract void fly();
}

class MallardDuck extends Duck { ... }
class RubberDuck extends Duck { ... }   // ゴム製のあひるは鳴かない → 困る

// ✅ Composition (機能を委譲)
interface QuackBehavior { void quack(); }
interface FlyBehavior   { void fly(); }

class Duck {
    private QuackBehavior quackBehavior;
    private FlyBehavior   flyBehavior;
    public Duck(QuackBehavior q, FlyBehavior f) {
        this.quackBehavior = q;
        this.flyBehavior   = f;
    }
    public void quack() { quackBehavior.quack(); }
    public void fly()   { flyBehavior.fly(); }
    public void setQuackBehavior(QuackBehavior q) { this.quackBehavior = q; }
}

// 実行時に切替可能
Duck rubber = new Duck(new Squeak(), new NoFly());
Duck mallard = new Duck(new RealQuack(), new FlyWithWings());

SOLID の Liskov Substitution Principle (LSP)

「親クラス T の変数に、サブクラス S のインスタンスを入れ替えても、プログラムが正しく動くべき」という原則。違反例:

class Rectangle {
    protected int width, height;
    public void setWidth(int w)  { this.width  = w; }
    public void setHeight(int h) { this.height = h; }
    public int area() { return width * height; }
}

// 「正方形は長方形の一種」と思って継承するとバグる
class Square extends Rectangle {
    @Override
    public void setWidth(int w) {
        this.width  = w;
        this.height = w;   // 同期させる
    }
    @Override
    public void setHeight(int h) {
        this.width  = h;
        this.height = h;
    }
}

// LSP 違反 → 親型変数で扱うとバグ
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(10);
System.out.println(r.area());   // 100 を期待するも 100 ではなく 50? いや 100 …
// 実は両方 10x10 になり 100 になるが、Rectangle のインタフェース契約
// 「width と height は独立」を破っている → 別箇所でバグる

final / sealed / 継承の制限

// 1. final class: 継承禁止
final class String { ... }        // 継承不可

// 2. final method: 上書き禁止
class Animal {
    public final void breathe() { ... }   // サブクラスで override 不可
}

// 3. private constructor + static factory
class Singleton {
    private Singleton() {}
    public static Singleton getInstance() { ... }
}

// 4. sealed class (Java 17+): 継承可能なサブクラスを限定
sealed abstract class Shape permits Circle, Square, Triangle {}
final class Circle extends Shape {}
final class Square extends Shape {}
final class Triangle extends Shape {}
// 第三者は Shape を継承不能 → pattern matching で網羅性チェック可能

Mixin (Python traits / PHP traits)

多重継承不可の言語で「コードの再利用」を実現する仕組み:

// PHP の trait
trait Loggable {
    public function log(string $msg): void {
        echo date('c') . " [" . static::class . "] $msg\n";
    }
}

trait Cacheable {
    public function cacheKey(): string {
        return static::class . ':' . $this->id;
    }
}

class User {
    use Loggable, Cacheable;
    public int $id = 1;
}

$u = new User();
$u->log("Hello");        // 2026-06-10T... [User] Hello
echo $u->cacheKey();     // User:1

設計指針 (まとめ)

  • 継承は 1-2 階層まで。深い継承は読みづらく、Fragile Base Class が起きやすい
  • 共通実装が大量にあるなら継承、共通インタフェースだけなら interface
  • 挙動を実行時に切替えたいなら Composition + Strategy
  • LSP を守る: 親型として扱われる前提のサブクラスを作る
  • final / sealed を積極利用 → 想定外の継承を防ぐ
  • クラス階層は「は (is-a)」で読んでみる。違和感があれば継承ではない

FAQ

Q: なぜ Java は多重継承を許さない?
A: Diamond Problem と Fragile Base Class の問題を避けるため。interface 多重実装 + default メソッドで代替可能。

Q: 継承と Composition、どちらを選ぶ?
A: 迷ったら Composition。「is-a」が自然な場合 + 親クラスがそれを前提に設計されているなら継承 OK。

Q: 親クラスの private フィールドはサブクラスで使える?
A: 使えません。サブクラスから使いたいなら protected (Java) / protected (C#) / _ プレフィクス (Python 慣習) で公開。