抽象クラス 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のみ(抽象メソッドの場合) |
実用的なガイドライン:抽象メソッドのみを持ち、状態を持たない抽象クラスを作成している場合、インターフェースの方がおそらくより良い選択肢です。
チャレンジ
簡単抽象クラスとインターフェースをいつ使用すべきかを示す車両システムを構築しましょう。抽象クラスを通じて共通の状態と動作を共有する車両をモデル化し、インターフェースを通じてオプションの機能を追加します。
コードは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() を出力する(インターフェースの機能を示す)
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造6インターフェースと抽象クラス
インターフェース入門インターフェースの実装複数インターフェースの実装インターフェースのデフォルトと静的メソッド抽象クラス vs インターフェース関数型インターフェースまとめ - 決済システム