こんにちは、かつコーチです。
前回は抽象クラスを解説しました。
今回は、抽象クラスと似ているようで役割が異なる「interface」の基本について解説します。
interfaceとは?
「実装すべき機能の約束事」を定義する
interface(インターフェース)とは、クラスが実装すべきメソッドの一覧を定義する仕組みです。
「このインターフェースを実装するクラスは、必ずこれらのメソッドを持つ」という約束事(契約)を表します。
public interface Flyable {
void fly(); // 中身のない抽象的なメソッド定義
}
interfaceを実装する側のクラスは、implementsキーワードを使います。
public class Bird implements Flyable {
private String name;
public Bird(String name) {
this.name = name;
}
@Override
public void fly() {
System.out.println(name + "が飛びました");
}
}
implements Flyableとすることで、Birdクラスは「fly()メソッドを必ず実装します」という約束を守ったことになります。
実装を忘れると、抽象クラスのときと同様にコンパイルエラーになります。
error: Bird is not abstract and does not override abstract method fly() in Flyable
なぜinterfaceが必要なのか
前回学んだ抽象クラスは1つしか継承できませんでした(Javaのクラスは多重継承不可)。
しかし、現実の物事は複数の性質を同時に持つことがよくあります。
例えば「鳥」は「飛べる」し「鳴ける」し、場合によっては「泳げる」こともあります。
interfaceは複数同時に実装できるため、こうした「複数の性質」を組み合わせて表現できます。
public interface Flyable {
void fly();
}
public interface Swimmable {
void swim();
}
public class Duck implements Flyable, Swimmable { // 複数のinterfaceを同時に実装できる
private String name;
public Duck(String name) {
this.name = name;
}
@Override
public void fly() {
System.out.println(name + "が飛びました");
}
@Override
public void swim() {
System.out.println(name + "が泳ぎました");
}
}
Duck(アヒル)クラスは、FlyableとSwimmableの両方の性質を実装できています。
クラスでは不可能だった「複数の型としてふるまう」ことが、interfaceなら実現できます。
interfaceとポリモーフィズムの活用
interface型の変数でまとめて扱う
interfaceも、以前解説したポリモーフィズムの恩恵を受けられます。
「飛べるもの」をFlyableという型でまとめて扱えます。
public class Main {
public static void main(String[] args) {
List<Flyable> flyables = new ArrayList<>();
flyables.add(new Bird("スズメ"));
flyables.add(new Duck("アヒル"));
for (Flyable flyable : flyables) {
flyable.fly(); // 実体がBirdでもDuckでも同じ呼び方でよい
}
}
}
スズメが飛びました
アヒルが飛びました
BirdとDuckは継承関係にない全くの別クラスですが、どちらもFlyableを実装しているため、同じList<Flyable>にまとめて格納できます。
これはクラスの継承だけでは実現できない、interfaceならではの柔軟性です。
よくあるつまずきポイント・エラー対処
interfaceのメソッドにアクセス修飾子を付けて混乱する
interface内のメソッドは、宣言時に修飾子を書かなくても自動的にpublic abstractとして扱われます。
これを知らずにprivateを付けてしまい、エラーに悩んだ経験があります。
❌Before
public interface Flyable {
private void fly(); // エラー:抽象メソッドはprivateにできない
}
error: modifier private not allowed here
✅After
public interface Flyable {
void fly(); // 修飾子は省略する(自動的にpublic abstractになる)
}
実際に私がこのエラーに遭遇したのは、クラスのメソッドをそのままinterfaceにコピーしたときでした。
クラスでは当たり前だったprivate修飾子が、interfaceの抽象メソッドでは使えないというルールを知らず、しばらくエラーメッセージの意味が理解できませんでした。
interfaceの抽象メソッドは「外部から呼び出せること」が前提のため、privateのような非公開の修飾子とは相性が悪いのだと理解してからは迷わなくなりました。
抽象クラスとinterfaceの使い分けで迷う
初心者が必ずぶつかるのが「抽象クラスとinterfaceはどちらを使えばいいのか」という疑問です。
判断軸を表にまとめます。
| 観点 | 抽象クラス | interface |
|---|---|---|
| 継承・実装できる数 | 1つだけ | 複数同時に実装可能 |
| フィールド(状態)を持てるか | 持てる | 基本的に持てない(定数のみ) |
| コンストラクタ | 持てる | 持てない |
| 使いどころ | 「is-a」関係が強く、共通の状態や初期化処理を持たせたい場合 | 「〜できる」という能力・役割を複数組み合わせたい場合 |
例えば「犬は動物である」という強い関係(is-a)には抽象クラス、「これは飛べる」「これは泳げる」という能力の組み合わせにはinterfaceを使う、という判断が基本になります。
応用・一歩先の使い方
interfaceは定数の置き場としても使える
interfaceの中に書いたフィールドは、自動的にpublic static final(変更不可の定数)として扱われます。
public interface GameConstants {
int MAX_PLAYERS = 4; // public static final int MAX_PLAYERS = 4; と同じ意味
}
public class Game implements GameConstants {
public void start() {
System.out.println("最大プレイヤー数: " + MAX_PLAYERS);
}
}
ただし、この使い方は「実装すべき機能」というinterface本来の目的から外れるため、多用は避け、関連する定数をまとめる補助的な用途にとどめるのが望ましいです。
まとめ
この記事のポイント
- interfaceはクラスが実装すべきメソッドの約束事を定義する仕組みで、
implementsで実装する - 1つのクラスは複数のinterfaceを同時に実装でき、クラスの多重継承の制約を補える
- interface内のメソッドは自動的に
public abstractになり、privateなどの修飾子は付けられない - 「is-a」の強い関係には抽象クラス、「〜できる」という能力の組み合わせにはinterfaceを使い分ける
次に読むべき記事
interfaceの基本を理解したら、次は実装を持てるメソッドを扱う「interfaceのデフォルトメソッド・静的メソッド」に進みましょう。
タグ: Java, 初心者向け, オブジェクト指向