Menu
Coddy logo textTech

抽象クラス vs インターフェース

CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 38/87。

抽象クラスとインターフェースの両方を理解したところで、よくある疑問が生じます。どちらをいつ使うべきでしょうか?その選択は、何をモデル化しようとしているかによって決まります。

クラスが共有された状態や振る舞いを持つ共通のベースを共有する場合は、抽象クラスを使用します。抽象クラスは、インスタンス変数、コンストラクタ、および抽象メソッドと具象メソッドの混在を持つことができます:

public 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();  // 実装が必須
}

無関係なクラスが共有できる機能を定義したい場合は、インターフェースを使用します。インターフェースは、オブジェクトが何であるかではなく、オブジェクトができることに焦点を当てます:

public interface Flyable {
    void fly();
}

// 無関係なクラスがこの機能を共有できます
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }

簡単な比較を以下に示します:

特徴抽象クラスインターフェース
インスタンス変数あり定数のみ
コンストラクタありなし
多重継承不可(単一継承 extends)可能(複数実装 implements)
アクセス修飾子任意publicのみ(抽象メソッドの場合)

実用的なガイドライン:抽象メソッドのみを持ち、状態を持たない抽象クラスを作成している場合、インターフェースの方がおそらくより良い選択肢です。

challenge icon

チャレンジ

簡単

抽象クラスとインターフェースをいつ使用すべきかを示す車両システムを構築しましょう。抽象クラスを通じて共通の状態と動作を共有する車両をモデル化し、インターフェースを通じてオプションの機能を追加します。

コードは5つのファイルに分けて構成します:

  • Vehicle.java: すべての車両の基礎となる抽象クラスを作成します。すべての車両は brand フィールド (String) と year フィールド (int) を持ちます。両方のフィールドを初期化するコンストラクタ、それぞれのゲッターメソッド、および String を返す抽象メソッド startEngine() を含めてください。また、[brand] ([year]) を返す具体的なメソッド getInfo() も追加します。これは抽象クラスの完璧なユースケースです。車両は状態(ブランド、年)と一部の動作(getInfo)を共有しますが、エンジンの始動方法はそれぞれ異なります。
  • Convertible.java: 屋根を変換できる車両用のインターフェースを定義します。この機能は車両が何であるか(is-a)に結びついているのではなく、特定の車両が「できること」(can-do)です。2つのメソッド openRoof()closeRoof() を宣言し、どちらも String を返すようにします。
  • Car.java: Vehicle を継承し、Convertible を実装するクラスを作成します。Car は追加の numDoors フィールド (int) を持ちます。継承されたフィールドを初期化するには super を使用してください。startEngine() を実装して [brand] car engine started を返すようにします。openRoof() を実装して [brand] roof opening を返し、closeRoof() を実装して [brand] roof closing を返すようにします。
  • Motorcycle.java: Vehicle を継承しますが、Convertible は実装しないクラスを作成します。オートバイには屋根がありません!Motorcycle は hasSidecar フィールド (boolean) を持ちます。startEngine() を実装して [brand] motorcycle engine roaring を返すようにします。
  • Main.java: すべてを組み合わせて、抽象クラスとインターフェースの違いを示します。車のブランド、車の年式、オートバイのブランド、オートバイの年式の4つの入力を受け取ります。

    Car(4ドア)とMotorcycle(サイドカーなし)を作成します。まず、両方の車両に対して getInfo() を出力することで、共有された抽象クラスの動作を示します。これらは両方とも Vehicle からこれを継承しています。次に、それぞれの startEngine() の結果を出力します。各車両タイプがこれをどのように異なって実装しているかに注目してください。

    最後に、インターフェースの機能を示します。Car だけが Convertible を実装しているため、Car に対してのみ openRoof() に続いて closeRoof() を呼び出して出力します。

4つの入力を受け取ります:車のブランド (String)、車の年式 (int)、オートバイのブランド (String)、オートバイの年式 (int)。

出力は合計6行になるはずです。両方の車両が抽象クラスの状態と getInfo() メソッドを共有している一方で、Car だけがコンバーチブル機能を持っていることに注目してください。これは、それぞれのアプローチをいつ使用すべきかを示しています!

チートシート

クラスが共通の状態や振る舞いを持つ共通のベースを共有する場合は、抽象クラス(abstract class)を使用します。抽象クラスは、インスタンス変数、コンストラクタ、および抽象メソッドと具象メソッドの組み合わせを持つことができます:

public 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();  // 実装が必須
}

無関係なクラス間で共有できる機能を定義したい場合は、インターフェース(interface)を使用します。インターフェースは、オブジェクトが何であるかではなく、何ができるかに焦点を当てます:

public interface Flyable {
    void fly();
}

// 無関係なクラスがこの機能を共有できる
class Bird extends Animal implements Flyable { ... }
class Airplane implements Flyable { ... }
class Drone implements Flyable { ... }
機能抽象クラスインターフェース
インスタンス変数あり定数のみ
コンストラクタありなし
多重継承不可(単一継承)可能(複数の実装)
アクセス修飾子任意publicのみ(抽象メソッドの場合)

ガイドライン: 抽象メソッドのみを持ち、状態を持たない抽象クラスを作成する場合は、インターフェースの方が適している可能性が高いです。

自分で試してみよう

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 入力を読み込む
        String carBrand = scanner.nextLine();
        int carYear = scanner.nextInt();
        scanner.nextLine(); // 改行文字を消費する
        String motorcycleBrand = scanner.nextLine();
        int motorcycleYear = scanner.nextInt();
        
        // TODO: 4ドアの Car を作成する
        
        // TODO: サイドカーなし (false) の Motorcycle を作成する
        
        // TODO: 両方の車両の getInfo() を出力する(共通の抽象クラスの振る舞いを示す)
        
        // TODO: 両方の車両の startEngine() を出力する(異なる実装を示す)
        
        // TODO: Car のみ openRoof() と closeRoof() を出力する(インターフェースの機能を示す)
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン