Javaの抽象クラスとインターフェースの違いとは?初心者でもわかる使い分けのポイント
生徒
「Javaで抽象クラスとインターフェースって何が違うんですか?」
先生
「どちらも共通の機能を定義するために使うんですが、それぞれ使い方や目的が違うんですよ。」
生徒
「使い分けが難しそうです…。どうすればいいですか?」
先生
「それでは、Javaの抽象クラスとインターフェースの違いや使い方について、わかりやすく解説していきましょう。」
1. 抽象クラスとは?
Javaの抽象クラス(abstract class)は、共通の処理や構造を持たせながら、詳細な実装はサブクラスに任せたいときに使います。抽象クラスは、abstractというキーワードを使って定義し、抽象メソッド(処理を持たないメソッド)や通常のメソッド(処理を含むメソッド)を含むことができます。
つまり、ある程度の共通処理を持たせつつ、派生クラスで自由にカスタマイズできるのが特徴です。
2. インターフェースとは?
Javaのインターフェース(interface)は、「こういう機能を持つべき」という契約のようなもので、実際の処理は持たず、メソッドの定義だけを記述します。クラスはこのインターフェースをimplementsして、すべてのメソッドを実装する必要があります。
ただし、Java 8以降ではdefaultやstaticメソッドを使うことで、インターフェースでも一部の実装を持つことができるようになりました。
3. 抽象クラスとインターフェースのコードでの違い
それでは、実際にJavaコードで違いを見てみましょう。まずは抽象クラスからです。
abstract class Animal {
public abstract void makeSound();
public void sleep() {
System.out.println("動物が眠っています...");
}
}
class Dog extends Animal {
public void makeSound() {
System.out.println("ワンワン!");
}
}
次に、インターフェースの例です。
interface AnimalInterface {
void makeSound();
}
class Cat implements AnimalInterface {
public void makeSound() {
System.out.println("ニャーニャー!");
}
}
インターフェースは複数のインターフェースを同時にimplementsできますが、抽象クラスはextendsで1つしか継承できません。
4. 機能と特徴の比較表
抽象クラスとインターフェースの違いを整理した表を確認しましょう。
| 項目 | 抽象クラス | インターフェース |
|---|---|---|
| キーワード | abstract class |
interface |
| 継承の制限 | 1つのみ | 複数可能 |
| 通常メソッド | 定義可能 | defaultやstaticで一部可能 |
| フィールド | 状態(変数)を持てる | 定数(public static final)のみ |
| コンストラクタ | あり | なし |
5. 抽象クラスとインターフェースの使い分け方
Javaで抽象クラスとインターフェースを使い分ける基準は、以下のように考えるとシンプルです。
- 共通処理を持たせたい → 抽象クラス
- 契約だけを定義したい(処理は各クラスに任せたい) → インターフェース
- すでに別のクラスを継承していて多重継承が必要 → インターフェース
- 状態(フィールド)を持たせたい → 抽象クラス
たとえば、図形を描画するプログラムでは、共通のプロパティや一部の動作を抽象クラスで定義し、それぞれの図形(円、四角など)の具体的な処理をサブクラスで実装するという形が考えられます。
6. Javaのバージョンによる違いと注意点
Java 8以降では、インターフェースでもdefaultメソッドを使うことで、処理を定義できるようになりました。ただし、この機能はあくまで補助的なものであり、共通処理を多く持たせたい場合はやはり抽象クラスのほうが向いています。
また、インターフェースに実装を書く場合は、defaultを忘れずにつける必要があります。付けないと抽象メソッド扱いになってしまい、全クラスで実装が求められます。
7. 抽象クラスとインターフェースを併用することも可能
Javaでは、抽象クラスを継承しつつ、インターフェースを複数実装するということも可能です。
abstract class Animal {
public void sleep() {
System.out.println("動物が寝ています");
}
}
interface Runnable {
void run();
}
class Dog extends Animal implements Runnable {
public void run() {
System.out.println("走っています!");
}
}
このようにすることで、共通処理を抽象クラスから継承しながら、動作に関する契約(インターフェース)も併用できます。