SJ blog
Java
Z

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

Javaのインターフェースと抽象クラスの使い分け完全解説

インターフェースと抽象クラスの違い、default メソッド、sealed interface、いつどちらを使うべきかを実例で解説します。

一言結論

現代のJavaでは「何ができるか(能力)」の定義はインターフェース、「何者であるか(状態付きの共通基盤)」の定義は抽象クラスと使い分けるのが原則で、迷ったらインターフェースを選ぶべきだ。

基本的な違い

特徴インターフェース抽象クラス
多重継承複数実装可能1クラスのみ継承
フィールドpublic static final のみ任意(インスタンス変数も可)
コンストラクタなしあり
アクセス修飾子public のみ(Java 9以降は private も可)任意
状態の保持できないできる

インターフェース

public interface Drawable {
    void draw();  // 暗黙的に public abstract
    
    default void drawWithBorder() {
        System.out.println("=== Border ===");
        draw();
        System.out.println("==============");
    }
    
    static Drawable noOp() {
        return () -> {};  // ファクトリメソッド
    }
}

public interface Resizable {
    void resize(double factor);
}

// 複数のインターフェースを実装できる
public class Circle implements Drawable, Resizable {
    private double radius;
    
    @Override
    public void draw() {
        System.out.println("○ (radius=" + radius + ")");
    }
    
    @Override
    public void resize(double factor) {
        this.radius *= factor;
    }
}

抽象クラス

public abstract class Animal {
    // インスタンス変数(状態)を持てる
    protected String name;
    protected int age;
    
    // コンストラクタを持てる
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 抽象メソッド(サブクラスで必ず実装)
    public abstract String sound();
    
    // 具体的な実装(共通処理)
    public void introduce() {
        System.out.println("私は" + name + "です。" + sound() + "と鳴きます。");
    }
}

public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }
    
    @Override
    public String sound() {
        return "ワン";
    }
}

Java 8以降の default メソッド

インターフェースに具体的な実装を持つメソッドを追加できます。

public interface Logger {
    void log(String message);
    
    default void logWithTimestamp(String message) {
        log(LocalDateTime.now() + " - " + message);
    }
    
    default void logError(String message, Throwable e) {
        log("[ERROR] " + message + ": " + e.getMessage());
    }
}

使いどころ: 既存のインターフェースに後方互換性を保ちながらメソッドを追加する場合。

Java 9以降の private メソッド

インターフェース内でのコードの共通化に使います。

public interface Validator<T> {
    boolean isValid(T value);
    
    default boolean isInvalidAndLog(T value) {
        if (!isValid(value)) {
            logValidationFailure(value);
            return true;
        }
        return false;
    }
    
    private void logValidationFailure(T value) {
        System.err.println("Validation failed for: " + value);
    }
}

Java 17の sealed interface

実装クラスを制限できます。

public sealed interface Shape
    permits Circle, Rectangle, Triangle {
    double area();
}

public final class Circle implements Shape {
    private final double radius;
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

permits で指定したクラス以外は実装できません。switch 式でのパターンマッチングと組み合わせると非常に強力です。

double area = switch (shape) {
    case Circle c -> Math.PI * c.getRadius() * c.getRadius();
    case Rectangle r -> r.getWidth() * r.getHeight();
    case Triangle t -> 0.5 * t.getBase() * t.getHeight();
};

どちらを使うべきか

インターフェースを選ぶ場合

  • 「できること(能力)」を定義する(Comparable, Serializable, Runnable
  • 複数の型の共通操作を定義する
  • 実装者に実装の自由を与えたい
  • 状態(フィールド)が必要ない
// 「何ができるか」を表現
interface Exportable {
    byte[] toBytes();
}

interface Importable {
    void fromBytes(byte[] data);
}

// 複数の能力を組み合わせられる
class Config implements Exportable, Importable { ... }

抽象クラスを選ぶ場合

  • 「何であるか(is-a 関係)」を定義する
  • 状態(インスタンス変数)を共有したい
  • コンストラクタで初期化が必要
  • サブクラスに共通の実装を提供したい(Template Method パターン)
// Template Method パターン
public abstract class DataProcessor {
    // テンプレートメソッド(処理の骨格を定義)
    public final void process() {
        readData();
        processData();  // サブクラスで実装
        writeResults();
    }
    
    protected abstract void processData();
    
    private void readData() { /* 共通処理 */ }
    private void writeResults() { /* 共通処理 */ }
}

まとめ

インターフェース: 契約(何ができるか)を定義する。多重実装可能で柔軟。

抽象クラス: 共通の実装と状態を持つ基底クラス。継承ヒエラルキーを構築する際に使う。

現代の Java 開発では、default メソッドの登場によりインターフェースの表現力が増しました。「まずインターフェースを検討し、共有状態や複雑な初期化が必要なら抽象クラス」という順序で考えると良いでしょう。