抽象クラスとインターフェイスの違いと使い分け【完全ガイド2025】
はじめに
オブジェクト指向プログラミングにおいて、抽象クラスとインターフェイスの使い分けは多くの開発者が迷うポイントです。この記事では、両者の違いから実践的な使い分けまで、具体例とともに詳しく解説します。
抽象クラスとは
抽象クラスは、共通の実装を持ちながら、一部のメソッドを抽象メソッドとして定義するクラスです。インスタンス化はできませんが、継承によって具体的な実装を強制できます。
抽象クラスの特徴
- インスタンス化不可
- 具体的なメソッドと抽象メソッドの両方を持てる
- フィールド(プロパティ)を持てる
- コンストラクタを持てる
- 単一継承のみ
抽象クラスの例(Java)
abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void sleep() { // 具体的な実装
System.out.println(name + " is sleeping");
}
public abstract void makeSound(); // 抽象メソッド
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void makeSound() {
System.out.println(name + " says Woof!");
}
}
インターフェイスとは
インターフェイスは、クラスが実装すべきメソッドの契約を定義する仕組みです。メソッドのシグネチャのみを定義し、実装は継承先のクラスに委ねます。
インターフェイスの特徴
- インスタンス化不可
- 抽象メソッドのみ定義(Java 8以降はデフォルトメソッドも可能)
- 定数のみ持てる(final static変数)
- 多重実装可能
- Java 8以降はdefaultメソッドとstaticメソッドも定義可能
インターフェイスの例(Java)
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
public void fly() {
System.out.println("Duck is flying");
}
public void swim() {
System.out.println("Duck is swimming");
}
}
抽象クラスとインターフェイスの比較表
| 項目 | 抽象クラス | インターフェイス |
|---|---|---|
| インスタンス化 | 不可 | 不可 |
| 実装メソッド | 可能 | Java 8以降は部分的に可能 |
| フィールド | 可能 | 定数のみ |
| コンストラクタ | 可能 | 不可 |
| 継承 | 単一継承 | 多重実装 |
| アクセス修飾子 | 自由 | public(基本) |
使い分けの基準
抽象クラスを使うべき場面
1. 共通実装がある場合
abstract class Shape {
protected String color;
public void setColor(String color) { // 共通実装
this.color = color;
}
public abstract double getArea(); // 各図形で異なる実装
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getArea() {
return Math.PI * radius * radius;
}
}
2. 段階的な機能拡張が必要な場合
abstract class Vehicle {
public void start() {
System.out.println("Engine started");
}
public abstract void accelerate();
}
abstract class Car extends Vehicle {
public void openDoor() {
System.out.println("Door opened");
}
}
class SportsCar extends Car {
public void accelerate() {
System.out.println("Accelerating rapidly!");
}
}
インターフェイスを使うべき場面
1. 契約の定義
interface PaymentProcessor {
boolean processPayment(double amount);
void sendReceipt(String email);
}
class CreditCardProcessor implements PaymentProcessor {
public boolean processPayment(double amount) {
// クレジットカード決済処理
return true;
}
public void sendReceipt(String email) {
// 領収書送信
}
}
2. 多重実装が必要な場合
interface Readable {
void read();
}
interface Writable {
void write();
}
class File implements Readable, Writable {
public void read() {
System.out.println("Reading file");
}
public void write() {
System.out.println("Writing file");
}
}
実践的な使い分けパターン
パターン1: テンプレートメソッドパターン
abstract class DataProcessor {
public final void process() { // テンプレートメソッド
loadData();
processData();
saveData();
}
protected abstract void loadData();
protected abstract void processData();
protected void saveData() { // 共通実装
System.out.println("Data saved");
}
}
パターン2: 戦略パターン
interface SortStrategy {
void sort(int[] array);
}
class BubbleSort implements SortStrategy {
public void sort(int[] array) {
// バブルソート実装
}
}
class QuickSort implements SortStrategy {
public void sort(int[] array) {
// クイックソート実装
}
}
言語別の特徴
Java 8以降の変更点
interface Calculator {
int add(int a, int b);
default int multiply(int a, int b) { // デフォルト実装
return a * b;
}
static int subtract(int a, int b) { // 静的メソッド
return a - b;
}
}
C#の場合
// 抽象クラス
abstract class BaseService {
protected string connectionString;
public abstract void Execute();
public void Log(string message) {
Console.WriteLine($"Log: {message}");
}
}
// インターフェイス
interface IRepository<T> {
void Save(T entity);
T FindById(int id);
}
よくある質問(FAQ)
Q: 抽象クラスとインターフェイスを両方使うべき?
A: はい。多くの場合、両方を組み合わせて使用します:
interface Drawable {
void draw();
}
abstract class UIComponent implements Drawable {
protected int x, y;
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
}
Q: どちらを選ぶか迷った場合は?
A: 以下の優先順位で判断してください:
- 多重継承が必要 → インターフェイス
- 共通実装がある → 抽象クラス
- 契約のみ定義したい → インターフェイス
- 迷った場合 → インターフェイス(より柔軟)
Q: パフォーマンスに差はありますか?
A: 実行時のパフォーマンスはほぼ同じです。設計の柔軟性とメンテナンス性を重視して選択してください。
ベストプラクティス
1. インターフェイス分離の原則
// 悪い例:肥大化したインターフェイス
interface Worker {
void work();
void eat();
void sleep();
}
// 良い例:分離されたインターフェイス
interface Workable {
void work();
}
interface Eatable {
void eat();
}
2. 抽象クラスでの共通実装の活用
abstract class BaseEntity {
protected Long id;
protected Date createdAt;
public BaseEntity() {
this.createdAt = new Date();
}
public abstract void validate();
}
3. インターフェイスでの依存性注入
class OrderService {
private final PaymentProcessor paymentProcessor;
public OrderService(PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}
まとめ
抽象クラスとインターフェイスの使い分けは、以下のポイントを押さえることが重要です:
抽象クラスを選ぶべき場合:
- 共通の実装コードがある
- 段階的な継承が必要
- is-a関係を表現したい
インターフェイスを選ぶべき場合:
- 契約や能力を定義したい
- 多重実装が必要
- can-do関係を表現したい
- より柔軟な設計にしたい
適切な使い分けにより、保守性が高く拡張しやすいコードを書くことができます。実際の開発では、両方を組み合わせて使用することが多いため、それぞれの特徴を理解して最適な選択をしてください。
関連記事
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座



