以前の記事では、クラスが「設計図」のようなもので、そこからオブジェクトという「実体」を作るんだ、という話をしましたね。今回はその続きとして、Javaのもうひとつの重要な要素である「インターフェース」について深掘りしていきましょう。
インターフェースって何?
インターフェースは一言でいうと「約束事」や「契約」を定義するものです。
「設計図」であるクラスは「どんなものが作れるか」を具体的に示していました。例えば、Carクラスなら「エンジンがあって、タイヤがあって、ハンドルがある」といった具体的な構造が書かれているイメージです。
でも、インターフェースは「何ができるべきか」という行動に焦点を当てます。具体的な実装は書かず、「こういう機能を持ってるべきだよね」ということを宣言するんです。
例えば、Drivable(運転可能)というインターフェースがあったとします。このインターフェースは「運転できるものは、startEngine()(エンジンをかける)とaccelerate()(加速する)という機能を持っていなければならない」と定めます。
Java
// Drivableインターフェースの定義
interface Drivable {
void startEngine(); // エンジンをかけるという約束
void accelerate(); // 加速するという約束
}
どうですか?interfaceというキーワードを使って、Drivableという名前のインターフェースを定義しています。中にはメソッドの宣言だけあって、具体的な処理({}の中身)は書かれていませんよね。これがインターフェースの大きな特徴です。
なんでインターフェースが必要なの?
「なんでそんな回りくどいことするの?クラスに直接書けばいいじゃん?」って思うかもしれませんね。インターフェースが必要な理由は大きく分けて2つあります。
1. 共通の行動を強制する
さっきのDrivableの例で考えてみましょう。CarクラスもMotorcycleクラスも「運転できる」という共通の行動を持っていますよね。もしインターフェースがなければ、それぞれのクラスで勝手にstartEngine()とかaccelerate()とかいう名前のメソッドを作ることになります。
もしかしたら、CarクラスではengineStart()、MotorcycleクラスではstartTheEngine()なんて、バラバラの名前になってしまうかもしれません。これだと、後から「運転できるもの」をまとめて扱いたいときに困りますよね。
そこでDrivableインターフェースの登場です。CarクラスもMotorcycleクラスもこのDrivableインターフェースを「実装」することで、「startEngine()とaccelerate()というメソッドを必ず持ちます」という約束を強制できるんです。
Java
// CarクラスがDrivableインターフェースを実装
class Car implements Drivable {
@Override // インターフェースのメソッドを実装するぞ!という目印
public void startEngine() {
System.out.println("車のエンジンがかかりました。ブォン!");
}
@Override
public void accelerate() {
System.out.println("車が加速します。ビューン!");
}
}
// MotorcycleクラスもDrivableインターフェースを実装
class Motorcycle implements Drivable {
@Override
public void startEngine() {
System.out.println("バイクのエンジンがかかりました。ドゥルルル!");
}
@Override
public void accelerate() {
System.out.println("バイクが加速します。ブィーン!");
}
}
implementsというキーワードを使って、インターフェースを実装していますね。@Overrideは「親のメソッドを上書きするぞ」という目印で、インターフェースのメソッドを実装するときにもよく使われます。
これで、CarもMotorcycleも、Drivableという共通の型として扱うことができるようになりました。
Java
public class DrivingSimulator {
public static void main(String[] args) {
Drivable myCar = new Car(); // CarオブジェクトをDrivable型として扱う
Drivable myBike = new Motorcycle(); // MotorcycleオブジェクトをDrivable型として扱う
System.out.println("--- 車を運転します ---");
myCar.startEngine();
myCar.accelerate();
System.out.println("\n--- バイクを運転します ---");
myBike.startEngine();
myBike.accelerate();
}
}
出力結果:
--- 車を運転します ---
車のエンジンがかかりました。ブォン!
車が加速します。ビューン!
--- バイクを運転します ---
バイクのエンジンがかかりました。ドゥルルル!
バイクが加速します。ブィーン!
myCarもmyBikeも型はDrivableなのに、それぞれ固有の動きをしているのがわかるでしょうか?これがインターフェースの持つ「ポリモーフィズム(多様性)」という力の一部です。同じメソッドを呼び出しても、オブジェクトの種類によって異なる振る舞いをします。

vscodeで実行した結果
2. 依存関係を減らす(疎結合にする)
もうひとつの重要な理由は「依存関係を減らす」こと、つまり「疎結合」にすることです。
例えば、あなたのゲームで「キャラクター」が「武器」を使うとします。もしSwordクラスに直接useSword()というメソッドを定義して、Warriorクラスがそれを使うようにしてしまうと、WarriorクラスはSwordクラスに直接「依存」してしまいます。
Java
// インターフェースを使わない例
class Sword {
public void swing() {
System.out.println("剣を振り回した!シュパッ!");
}
}
class Warrior {
private Sword mySword;
public Warrior() {
this.mySword = new Sword(); // WarriorがSwordに依存
}
public void attack() {
mySword.swing();
}
}
これだと、もし「弓」を使いたくなったらどうでしょう?Warriorクラスを書き換えて、Bowクラスのインスタンスを持つように変更しなければなりません。
Java
// Bowクラスを追加
class Bow {
public void shootArrow() {
System.out.println("弓矢を放った!ヒュン!");
}
}
// Warriorクラスを書き換える必要あり
class Warrior {
// private Sword mySword;
private Bow myBow; // SwordからBowに変更
public Warrior() {
// this.mySword = new Sword();
this.myBow = new Bow(); // Bowを生成
}
public void attack() {
// mySword.swing();
myBow.shootArrow(); // メソッドも変更
}
}
これは面倒ですよね。
そこでインターフェースの出番です。「使える武器は攻撃できる」という共通の約束を定義します。
Java
// Attackableインターフェースの定義
interface Attackable {
void attack(); // 攻撃するという約束
}
// SwordクラスがAttackableインターフェースを実装
class Sword implements Attackable {
@Override
public void attack() {
System.out.println("剣を振り回した!シュパッ!");
}
}
// BowクラスもAttackableインターフェースを実装
class Bow implements Attackable {
@Override
public void attack() {
System.out.println("弓矢を放った!ヒュン!");
}
}
// WarriorクラスはAttackableインターフェースに依存
class Warrior {
private Attackable currentWeapon; // 武器はAttackable型
public Warrior(Attackable weapon) { // コンストラクタで武器を受け取る
this.currentWeapon = weapon;
}
public void useWeapon() {
currentWeapon.attack(); // どの武器でもattack()メソッドを呼べばOK
}
public void changeWeapon(Attackable newWeapon) {
this.currentWeapon = newWeapon;
System.out.println("武器を" + newWeapon.getClass().getSimpleName() + "に変更しました。");
}
}
public class GameCharacter {
public static void main(String[] args) {
Warrior arthur = new Warrior(new Sword()); // 剣を持たせてアーサーを生成
System.out.println("アーサー、攻撃!");
arthur.useWeapon();
System.out.println("\nアーサー、弓に持ち替え!");
arthur.changeWeapon(new Bow()); // 弓に持ち替え
System.out.println("アーサー、攻撃!");
arthur.useWeapon();
}
}
出力結果:
アーサー、攻撃!
剣を振り回した!シュパッ!
アーサー、弓に持ち替え!
武器をBowに変更しました。
アーサー、攻撃!
弓矢を放った!ヒュン!
どうでしょうか?Warriorクラスは、具体的なSwordやBowというクラスを知る必要がなくなりました。ただ「Attackableなものなら何でも使える」というだけです。これにより、新しい武器(例えばMagicStaffなど)を追加しても、Warriorクラスを修正する必要がなくなります。これが「疎結合」のメリットです。コードの変更に強い、拡張しやすいプログラムになるんですね。

vscodeで実行した結果
インターフェースのちょっと応用
Java 8以降、インターフェースには「デフォルトメソッド」や「staticメソッド」も書けるようになりました。これはちょっと応用的な話ですが、少しだけ触れておきましょう。
- デフォルトメソッド: インターフェースを実装するクラスが、そのメソッドを実装しなくても済むように、デフォルトの実装を提供できます。既存のインターフェースに新しいメソッドを追加する際に、過去のクラスを壊さずに機能を追加できるので便利です。
- staticメソッド: インターフェース自体が持つユーティリティメソッドなどを定義できます。
最初は「メソッドの宣言だけ」という基本をしっかり押さえておけば十分です!
まとめ
インターフェースは「約束事」や「契約」を定義するものです。
- インターフェースは「何ができるべきか」を宣言し、具体的な実装は書きません。
- クラスは
implementsキーワードを使ってインターフェースを「実装」し、そのインターフェースが定義したメソッドを必ず提供します。 - これにより、複数のクラスに共通の行動を強制できます。
- さらに、具体的なクラスへの依存関係を減らし、柔軟で変更に強い(疎結合な)プログラムを作ることができます。
Javaでプログラムを作る上で、インターフェースは避けて通れない非常に重要な概念です。最初は難しく感じるかもしれませんが、「共通の振る舞いを定義する」「約束を強制する」というイメージを持てば、理解が深まるはずです。


コメント