4.

抽象クラス完全ガイド — interface との違いと使い分け

編集
この記事の要点
  • 抽象クラス (abstract class) はインスタンス化できないクラスで、サブクラスに共通処理実装強制を同時に与える
  • abstract メソッドを 1 つでも持つクラスは abstract にせねばならず、サブクラスは override が必須
  • interface との違い: 抽象クラスはフィールド・実装済メソッドを持てる / interface は契約のみ。多重継承は interface のみ可
  • Java 8+ の default メソッドにより interface も実装を持てるようになり、両者の境界は曖昧化(ただしフィールド非保持 / 状態を持てない)
  • Template Method パターン: 共通アルゴリズムを親で定義、変動部分を abstract メソッドにして子で実装 — 抽象クラスの代表的活用
  • final との対比: abstract は「継承前提」 / final は「継承禁止」で対極

抽象クラスとは

抽象クラス (abstract class) は、そのままではインスタンス化できないクラスです。サブクラスで継承して具象化することを前提に作られ、共通フィールドと実装を提供しつつ、特定のメソッドの実装をサブクラスに強制します。

OOP では「テンプレートを作り、変動する部分だけ子クラスに任せたい」という場面が頻出します。これを言語仕様レベルで強制するのが抽象クラスの役割です。

基本構文 (Java)

// 抽象クラスの定義
public abstract class Shape {
    protected String name;

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

    // 実装済みメソッド (サブクラスで共通利用)
    public String describe() {
        return name + " の面積は " + area();
    }

    // 抽象メソッド (サブクラスで実装必須)
    public abstract double area();
}

// サブクラスは abstract メソッドを必ず override
public class Circle extends Shape {
    private double r;

    public Circle(double r) {
        super("円");
        this.r = r;
    }

    @Override
    public double area() {
        return Math.PI * r * r;
    }
}

// 利用
Shape s = new Shape("X");      // ← コンパイルエラー: abstract をインスタンス化不可
Shape c = new Circle(3.0);     // OK: 具象サブクラスはインスタンス化可能
System.out.println(c.describe());  // → 円 の面積は 28.27...

PHP / C# での書き方

name = $name;
    }

    public function describe(): string {
        return "{$this->name} の面積は " . $this->area();
    }

    // 抽象メソッド
    abstract public function area(): float;
}

class Circle extends Shape {
    public function __construct(private float $r) {
        parent::__construct("円");
    }

    public function area(): float {
        return M_PI * $this->r ** 2;
    }
}

$c = new Circle(3.0);
echo $c->describe();
// C# の抽象クラス
public abstract class Shape {
    protected string Name;
    protected Shape(string name) => Name = name;

    public string Describe() => $"{Name} の面積は {Area()}";

    // C# は abstract キーワードで宣言
    public abstract double Area();
}

public class Circle : Shape {
    private double r;
    public Circle(double r) : base("円") { this.r = r; }

    // override キーワード必須
    public override double Area() => Math.PI * r * r;
}

interface との違い

項目抽象クラスinterface
インスタンス化不可不可
フィールド持てる(状態あり)定数のみ(Java)/ 通常持てない
実装済みメソッド持てるJava 8+ default で可 / C# 8+ default で可
コンストラクタ持てる(サブクラスから super 呼出)持てない
多重継承×(単一継承のみ)○(複数 implements 可)
用途「is-a」+ 共通実装の共有「振る舞い契約」だけを規定

原則は「共通状態や処理を共有したい → 抽象クラス / 異なる継承ツリーに同じ契約を貼りたい → interface」です。

Java 8+ default メソッドで interface が抽象クラスに迫る

// Java 8+: interface に default 実装を持てる
public interface Greetable {
    String name();

    // default メソッド: 実装を持つ
    default String greet() {
        return "Hello, " + name();
    }
}

public class User implements Greetable {
    @Override public String name() { return "Alice"; }
}

new User().greet();  // → Hello, Alice

これにより「実装の共有」も interface でできるようになりましたが、状態(フィールド)を持てない点が決定的に異なります。状態が必要なら抽象クラス、状態不要なら interface です。

Template Method パターンでの活用

抽象クラスの古典的活用が Template Method パターンです。アルゴリズムの骨格を親で定義し、変動部分のみ子に委ねます:

// テンプレート: データ処理パイプラインの骨格
public abstract class DataProcessor {
    // 公開メソッド (final にして上書き禁止)
    public final void run() {
        var data = load();
        var processed = process(data);
        save(processed);
    }

    protected abstract List load();
    protected abstract List process(List data);
    protected abstract void save(List data);
}

// 具象 1: CSV 用
public class CsvProcessor extends DataProcessor {
    protected List load() { /* CSV 読み込み */ }
    protected List process(List d) { /* 変換 */ }
    protected void save(List d) { /* CSV 出力 */ }
}

// 具象 2: JSON 用
public class JsonProcessor extends DataProcessor {
    /* 同じ run() が JSON 用処理を実行する */
}

ストラテジーパターンとの比較

振る舞いの差し替えだけが目的ならストラテジー(interface 経由)のほうが疎結合です。状態・共通処理を共有したいなら抽象クラスを選びます:

  • ストラテジー: 振る舞い (Strategy interface) を Context にコンポジションする → 実行時切替が容易
  • Template Method: 継承で固定 → コンパイル時に決まる、コードの重複削減に強い

final と abstract の対比

修飾子クラスメソッド意図
abstractインスタンス化不可、継承前提実装なし、サブクラスで実装必須「継承して使え」
final継承不可override 不可「これ以上変えるな」

同一メソッドに abstractfinal を同時に付けるのは矛盾なのでコンパイルエラーになります。

使うべき場面 / 避けるべき場面

  • 使うべき: 共通フィールド・共通処理を持ちつつ、一部メソッドだけサブクラスごとに変えたい
  • 使うべき: Template Method / Factory Method パターン適用時
  • 避ける: 単一の「振る舞い契約」しか必要ないとき → interface のほうが疎結合
  • 避ける: ライブラリ公開 API の上位型 → 抽象クラスは進化させづらい(メソッド追加でサブクラス全滅)。interface + default のほうが安全

FAQ

Q: 抽象クラスにコンストラクタって意味あるの?
A: あります。サブクラスから super(...) で呼び出され、共通フィールドの初期化に使われます。

Q: abstract メソッドのない abstract クラスは作れる?
A: 作れます。「インスタンス化させたくない基底クラス」として使えます。

Q: PHP / Java / C# 以外でも同じ?
A: Kotlin・Scala・TypeScript も abstract キーワードあり。Python は abc.ABC + @abstractmethod で同等を実現します。Go は abstract クラスを持たず interface のみで設計します。

Q: 多重継承したいときは?
A: 多くの言語で抽象クラスは単一継承のみ。複数の役割を継承したい場合は interface を組み合わせるのが定石です(Java では extends AbstractBase implements I1, I2)。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. オブジェクト指向の概念
  2. 継承の概念と必要性
  3. ポリモーフィズム(多様性)の概念と必要性
  4. 抽象クラスの概念と必要性
  5. インターフェースの概念と必要性
  6. カプセル化の概念と必要性