PR

Java:インターフェースとは?わかりやすく3分で解説(interface/implements)

Java

Javaのインターフェースは「契約」としての役割を持ち、クラスが必ず実装すべきメソッドのシグネチャ(メソッド名、引数、戻り値の型)を定義するための仕組みです。

リモコンのボタンがある程度決まっているように、インターフェースを実装するクラスはその契約に従ってメソッドを実装する必要がある、みたいなイメージです。リモコンインターフェースを利用するクラスは、必ず「電源ボタン」は実装するようにしてね!みたいなことをするのがインターフェースです。

このページではインターフェースって何?という初心者向けにその意味や概念、使い方を1から順を追って解説します。

スポンサーリンク

インターフェース(interface)とは何か

Javaにおけるインターフェースは、「契約(コントラクト)」とも呼ばれ、クラスが必ず提供しなければならないメソッドの名前、引数、戻り値の型を定義する仕組みです。インターフェースで実装すべきメソッドのセットを定義し、そのルールに沿ってクラスが具体的な処理を実装する流れです。

リモコンのボタンの配置がすべての家電で統一されているように、インターフェースは各クラスが持つべき操作を決めています。これにより、どのクラスも同じ操作(メソッド)を持つことが保証され、プログラム全体が一貫した構造となります。

なぜinterfaceが必要なのか

  • コードの設計が明確に
    インターフェースを用いることで、「このクラスは必ずこういった操作を持つ」という契約が明文化され、後からコードを読む人や利用する側に安心感を与えます。
  • 疎結合なシステム設計
    クラス間の依存関係を具体的な実装ではなく抽象的なインターフェースでつなぐことで、部品同士の結びつきが弱くなり、システム全体の変更に強くなります。たとえば、後で実装を変更しても、インターフェースを利用している部分には影響が出にくい設計になります。
  • 多重実装による柔軟性
    Javaはクラスの多重継承を許しませんが、複数のインターフェースは実装可能です。これにより、クラスに対して複数の役割や機能を持たせることができ、非常に柔軟な設計が可能になります。

interfaceの基本的な定義と実装

インターフェースは interface キーワードを使って定義します。
例えば、以下は動物が持つべき基本的な動作を示すインターフェースです。

public interface Animal {
    // 定数(暗黙的に public static final)
    int MAX_AGE = 100;

    // 抽象メソッド(暗黙的に public abstract)
    void makeSound();
    void move();
}

基本的な考え方としてはインターフェースのメソッドは実装を持たずpublic abstractが暗黙的に付与されます。実装はクラスが行います。(実践的に例外がありますが、これは後述します。)

よって、void makeSound(){}; と記述してしまうと、これは「何の処理もしない」という意味の実装が定義されてしまうため、コンパイルエラーになります。

また、インターフェースではフィールドの定義は可能ですが、すべてのフィールドは暗黙的にpublic static final(定数)になります。つまり、変数の値を変更することはできません。

ポイント インターフェースのフィールドの特徴

  1. public(公開)
    • すべてのクラスからアクセス可能。
  2. static(静的)
    • インスタンスを作成しなくても利用可能。
  3. final(定数)
    • 値の変更ができない。

クラスでの実装方法:implements

クラスがインターフェースを実装する際は、implements キーワードを使用します。以下は、Animal インターフェースを実装した Dog クラスの例です。

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("ワンワン");
    }

    @Override
    public void move() {
        System.out.println("走る");
    }
}

実装クラスはインターフェースで定義されたすべてのメソッドを実装する必要があります。インターフェースでは、以下の通り2つのメソッドを実装することを「契約」として定義しているため、そのインターフェースを実装するクラスでは必ず「makeSound()」と「move()」の2つのメソッドを実装しないといけない、ということです。

public interface Animal {
    // 定数(暗黙的に public static final)
    int MAX_AGE = 100;

    // 抽象メソッド(暗黙的に public abstract)
    void makeSound();
    void move();
}

このようにちゃんと契約を守ったクラスが実装されることを保証するための仕組みがインターフェースです。

インターフェースの継承:extends

Javaでは、インターフェース同士も継承(拡張)が可能です。
クラスの場合は「implements」を使いますが、インターフェース同士の場合は「extends」を使用します。

参考 クラスの継承とは?

インターフェース継承の基本

  • 1つ以上のインターフェースを継承可能
    あるインターフェースが、他のインターフェースを拡張する場合、拡張先のインターフェースで定義された全メソッドを引き継ぎます。
  • 実装クラスでの実装
    拡張されたインターフェースを実装するクラスは、すべての継承元インターフェースのメソッドを実装しなければなりません。

継承の例

以下は、基本の Animal インターフェースを拡張して、ペット特有の機能を追加する例です。

// 基本の動物の契約
public interface Animal {
    void makeSound();
    void move();
}

// Animalを拡張して、ペットならではの機能を追加
public interface Pet extends Animal {
    void play();
}

この状態で、Pet を実装するクラスは、makeSound()move() に加えて、play() も実装する必要があります。

public class Cat implements Pet {
    @Override
    public void makeSound() {
        System.out.println("ニャー");
    }
    
    @Override
    public void move() {
        System.out.println("静かに歩く");
    }
    
    @Override
    public void play() {
        System.out.println("ボールで遊ぶ");
    }
}

インターフェースの多重継承

インターフェースは複数のインターフェースを同時に継承することも可能です。
たとえば、次のように複数のインターフェースを統合して、新たな契約を定義できます。

public interface Flyable {
    void fly();
}

public interface Swimmable {
    void swim();
}

// 多重継承:BirdはAnimal、Flyable、Swimmableのすべての契約を引き継ぐ
public interface Bird extends Animal, Flyable, Swimmable {
    // 追加のメソッドを定義することも可能
    void layEggs();
}

このように、クラスが Bird インターフェースを実装すれば、makeSound()move()fly()swim()layEggs() のすべてを実装する必要があります。

以上がJavaのインターフェースの基本です。ここからは、この基本を押さえたうえでの実践編の解説に進みます。

インターフェースに実装できるメソッドとその他実践例

基本的に、インターフェースのメソッドは抽象的(具体的な実装を持たない)ですが、Java 8以降では一部例外が存在します。ここからはそれらのメソッドについてご説明します。

defaultメソッド

Java 8以降、インターフェースは default メソッドを持つことができ、実装クラスで必ずオーバーライドしなくても動作する既定の処理を提供できるようになりました。
これは、インターフェースを拡張して新たなメソッドを追加する際、既存の実装に影響を与えないようにするために非常に有用です。

public interface Greeting {
    void sayHello();

    default void sayGoodbye() {
        System.out.println("さようなら");
    }
}

default を付与することで、「sayGoodbye()」メソッドは実装クラスでオーバーライドしなくても動作する既定の処理を行うようになります。「オーバーライドしてもよいけど、オーバーライドしなかったらこういう動きね!」をやりたいときに使うのがdefaultメソッドです。

ちなみに、defaultメソッドもインターフェースに定義するメソッドと同様に、自動的にpublicで修飾されます。

staticメソッド

また、インターフェース内で static メソッドを定義することで、インターフェースに関連するユーティリティ処理をまとめることができます。(参考 staticとは?
これらは実装クラスに依存せず、インターフェース名を通じて直接呼び出せます。

interface MathUtil {
    static int square(int x) {
        return x * x;
    }
}

public class InterfaceStaticTest {
    public static void main(String[] args) {
        int result = MathUtil.square(5);
        System.out.println(result); // 出力: 25
    }
}

MathUtil.square(5) のように、インターフェース名.メソッド名() で呼び出せます。

  1. クラスのようにインスタンス化不要
    • staticメソッドはインターフェースのインスタンスを作らずに呼び出せる。
  2. オーバーライド不可
    • 実装クラスではstaticメソッドをオーバーライドできない。
  3. publicで定義するのが一般的
    • private static(Java 9以降)も可能だが、外部からは呼べない。

プライベートメソッド(private)

Java 9以降、インターフェースにprivateメソッドを定義できるようになりました。これは、デフォルトメソッド(default)やstaticメソッドのコードを整理するために使われます。

interface MyInterface {
    default void showMessage() {
        System.out.println(getGreeting()); // privateメソッドを呼び出す
    }

    private String getGreeting() { // privateメソッド
        return "こんにちは!";
    }
}

public class PrivateMethodTest implements MyInterface {
    public static void main(String[] args) {
        PrivateMethodTest test = new PrivateMethodTest();
        test.showMessage(); // 出力: こんにちは!
    }
}

getGreeting()privateなので外部から直接呼べないが、defaultメソッド内で利用可能になります。

interface Utility {
    static void printMessage() {
        System.out.println(getStaticGreeting());
    }

    private static String getStaticGreeting() {
        return "Hello from private static!";
    }
}

public class StaticMethodTest {
    public static void main(String[] args) {
        Utility.printMessage(); // 出力: Hello from private static!
    }
}

getStaticGreeting()private static なので、printMessage()の内部でのみ使用可能になります。

  1. 外部や実装クラスからは呼び出せない
    • privateなので、インターフェース内部でのみ利用可能
  2. defaultメソッドやstaticメソッドの共通処理をまとめる
    • コードの重複を防ぎ、メンテナンス性を向上させる。
  3. defaultメソッドから呼び出し可能
    • defaultメソッド内で処理を共通化できる。

インターフェースと抽象クラスの使い分け

  • インターフェースは、「実装の契約」として利用し、複数のクラスに共通のメソッドのシグネチャだけを定義します。
    • 状態(フィールド)は持たず、定数のみ。
    • 複数実装が可能なため、役割ごとに分けやすい。
  • 抽象クラス参考 抽象クラスとは?)は、部分的な実装や共通の状態(フィールド)を持つ場合に利用します。
    • 単一継承の制約があるため、使いどころを選びます。

名前の衝突とデフォルトメソッドの対処

複数のインターフェースを実装または継承している場合、同じシグネチャの default メソッドが存在するときは、実装クラス側で明示的にオーバーライドして衝突を解決する必要があります。

public interface A {
    default void greet() {
        System.out.println("こんにちは from A");
    }
}

public interface B {
    default void greet() {
        System.out.println("こんにちは from B");
    }
}

public class MyClass implements A, B {
    @Override
    public void greet() {
        // 必要に応じてどちらか、または独自の処理を実装
        System.out.println("こんにちは from MyClass");
    }
}

コーディングの実践的なアドバイス

  • 小さな単位でテストする
    インターフェースとその実装は、最初はシンプルな例から試し、コンパイルエラーや実行結果を確認しながら理解を深めるとよいでしょう。
  • 拡張性を意識する
    インターフェースは、将来的に新しい機能を追加する際にコードの修正範囲を最小限に抑えられるため、設計段階からどのような役割に分けるかを検討してください。
  • 設計パターンと組み合わせる
    インターフェースは、Strategyパターン、Observerパターン、Dependency Injection などの設計パターンと組み合わせることで、より柔軟で拡張性の高いプログラム設計が可能になります。

まとめ Javaのインターフェースとは?

  1. 基本概念と利用方法
    • インターフェースは、クラスが実装すべきメソッドのシグネチャを定義する「契約」です。
    • クラスは implements を用いてインターフェースを実装し、利用側はインターフェース型で操作できます。
  2. インターフェースの継承
    • インターフェース同士は extends キーワードで継承でき、複数のインターフェースを一つにまとめることができます。
    • 拡張されたインターフェースを実装するクラスは、継承元すべてのメソッドを実装する必要があります。
  3. Java 8以降の拡張機能
    • defaultメソッドによって、既定の実装をインターフェース内に持たせることができ、後からのメソッド追加時に互換性を保てます。
    • staticメソッドは、ユーティリティ的な処理をインターフェースにまとめるのに役立ちます。
  4. コーディング時に留意すべき点
    • インターフェースと抽象クラスの使い分け(状態を持たせたいか、実装の契約だけにしたいか)。
    • 複数のインターフェースの継承・実装時に起こる名前の衝突への対処(明示的なオーバーライド)。
    • 設計パターンやSOLID原則に基づいた、拡張性・保守性の高い設計。

インターフェースの基本的な理解と継承の仕組みを押さえておくことで、将来的に大規模なプログラムやフレームワークを設計・開発する際にも、柔軟で再利用性の高いコードを書くことが可能となります。まずはシンプルな例から実際に手を動かし、徐々に複雑な設計にも挑戦してみてください。

タイトルとURLをコピーしました