Javaのinterfaceを完全マスター!10選の使い方とサンプルコード

JavaのinterfaceのイラストとサンプルコードJava
この記事は約23分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めば、Javaのinterfaceを使ったプログラミングができるようになります。

プログラミングにおいて、interfaceは設計図のような役割を果たします。でも正直、初めてinterfaceに触れたときって何これ?と思った方も多いはず。

この記事では、Javaでinterfaceを使いこなす10選の方法と、具体的なサンプルコードをご紹介します。

特に「使い方がよく分からない」「いつ使うのかが不明」などとお悩みの方は、必見です。

●Javaのinterfaceとは

Javaにおいてinterfaceは非常に重要な概念です。

しかし、その重要性とは裏腹に、多くの初心者はこのinterfaceが何なのか、どのように使うのかについて少し混乱してしまうことがよくあります。

○interfaceの基本的な概念

interfaceとは、一言で言えば「契約」や「設計図」です。

この契約に従って、クラス(設計図に基づいて建造される「建物」のようなもの)を作るわけです。

interface内にはメソッドのシグニチャ(名前、引数、戻り値)だけが定義され、実際の処理(実装)は含まれません。

したがって、そのinterfaceを「実装」するクラスは、interfaceで指定されたメソッドの実装を提供する必要があります。

Javaでは、interfaceを定義するにはinterfaceキーワードを用います。

そして、そのinterfaceを実装するクラスはimplementsキーワードを使用します。

○interfaceの歴史と背景

interfaceはオブジェクト指向プログラミングが広まるにつれて、クラスの設計において重要な要素とされてきました。

Javaが初めて登場した1995年から存在しており、Javaが多くのシステムで利用される理由の一つでもあります。

interfaceを使うことで、柔軟かつメンテナンス性の高いコードを書くことが可能になります。

特に大規模なプロジェクトでは、このinterfaceがクラスやメソッドの設計において重要な役割を果たしています。

interfaceの使い方、実用例、注意点、カスタマイズ方法まで、この記事ではこれから詳細にわたって説明します。

そうした基本的な情報から、具体的なコーディングの際のテクニックまで、interfaceを理解して使いこなすための全てを解説していきます。

●Javaでのinterfaceの使い方

いよいよJavaでのinterfaceの使い方について深掘りしていきます。

ここでは、interfaceを定義する基本的な方法から、複数のinterfaceを同時に実装する方法まで、具体的なサンプルコードを交えて解説します。

○サンプルコード1:基本的なinterfaceの定義と実装

最初は基本中の基本、interfaceをどのように定義し、それをクラスで実装するのかという部分です。

// シンプルなinterfaceの定義
interface Animal {
    // 鳴き声を出力するメソッド
    void makeSound();
}

// DogクラスでAnimal interfaceを実装
public class Dog implements Animal {
    // makeSoundメソッドをオーバーライド
    public void makeSound() {
        System.out.println("ワンワン");
    }

    public static void main(String[] args) {
        // Dogクラスのインスタンスを生成
        Dog myDog = new Dog();

        // makeSoundメソッドを呼び出す
        myDog.makeSound();
    }
}

このコードでは、Animalという名前のinterfaceを作っています。

その後で、DogクラスでこのAnimal interfaceを実装しています。

makeSoundというメソッドをオーバーライドして、犬特有の「ワンワン」という音を出力するようにしています。

このコードを実行すると、コンソールには「ワンワン」と表示されます。

このように、interfaceを使うことで、特定の動作(この場合は「鳴く」)を強制できるわけです。

○サンプルコード2:複数のinterfaceの実装

Javaでは一つのクラスが複数のinterfaceを実装することも可能です。

// 鳴き声を出力するAnimal interface
interface Animal {
    void makeSound();
}

// 移動方法を出力するMovable interface
interface Movable {
    void move();
}

// Dogクラスで複数のinterfaceを実装
public class Dog implements Animal, Movable {
    // Animal interfaceのmakeSoundメソッドをオーバーライド
    public void makeSound() {
        System.out.println("ワンワン");
    }

    // Movable interfaceのmoveメソッドをオーバーライド
    public void move() {
        System.out.println("走る");
    }

    public static void main(String[] args) {
        // Dogクラスのインスタンスを生成
        Dog myDog = new Dog();

        // makeSoundとmoveメソッドを呼び出す
        myDog.makeSound();
        myDog.move();
    }
}

この例ではAnimal interfaceとMovable interfaceの二つのinterfaceを一つのDogクラスで実装しています。

それぞれのメソッド(makeSoundmove)をオーバーライドしています。

このコードを実行すると、コンソールには「ワンワン」と続けて「走る」と表示されます。

一つのクラスで複数のinterfaceを実装することで、そのクラスが持つべき動作や特性をより詳細に指定できます。

○ サンプルコード3:defaultメソッドとstaticメソッドの使用

Java 8以降で導入されたinterfaceの拡張機能として、defaultメソッドとstaticメソッドがあります。

これにより、interfaceにもメソッドの実装を追加できるようになりました。

□defaultメソッド

defaultメソッドを使用すると、interfaceに新しいメソッドを追加しても、既存のクラスに影響を与えずに済みます。

// defaultメソッドの使用例
interface Animal {
    void makeSound();

    // defaultメソッド
    default void sleep() {
        System.out.println("Zzz...");
    }
}

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

    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.makeSound(); // "ワンワン"と出力
        myDog.sleep(); // "Zzz..."と出力
    }
}

このコードではAnimal interfaceにsleepというdefaultメソッドを追加しています。

Dogクラスはこの新しいメソッドを自動的に継承します。mainメソッドでmyDog.sleep();と呼び出すと、”Zzz…”が出力されます。

□staticメソッド

また、interface内で共有されるユーティリティメソッドを定義する場合、staticメソッドが便利です。

// staticメソッドの使用例
interface Animal {
    void makeSound();

    static void run() {
        System.out.println("走る");
    }
}

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

    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.makeSound(); // "ワンワン"と出力
        Animal.run(); // "走る"と出力
    }
}

このサンプルコードではAnimal interfaceにrunというstaticメソッドを定義しています。

このrunメソッドはAnimal.run();として呼び出します。この場合、「走る」と出力されます。

○サンプルコード4:privateメソッドの利用

Java 9以降、interface内でprivateメソッドを使用できるようになりました。

これにより、共通のロジックをまとめることが可能です。

// privateメソッドの使用例
interface Animal {
    void makeSound();

    default void sleep() {
        log("Zzz...");
    }

    private void log(String message) {
        System.out.println("Animal: " + message);
    }
}

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

    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.makeSound(); // "ワンワン"と出力
        myDog.sleep(); // "Animal: Zzz..."と出力
    }
}

このサンプルでは、logというprivateメソッドをAnimal interfaceに追加しています。

このメソッドは、sleepメソッド内から呼び出されており、”Animal: Zzz…”と出力されます。

●interfaceの応用例

Javaでのinterfaceの活用は基本的な使い方にとどまりません。

設計パターンや高度なプログラミングテクニックでもよく使用されます。

ここでは、AdapterパターンとStrategyパターンの二つを取り上げ、具体的なコード例とともに説明します。

○サンプルコード5:adapterパターンの実装

Adapterパターンは、既存のクラスが提供するAPIとクライアントが期待するAPIが異なる場合に、それらを適合させる設計パターンです。

interfaceを活用することで、この問題をエレガントに解決できます。

// Adapterパターンの例
interface NewSystem {
    void newMethod();
}

class OldSystem {
    public void oldMethod() {
        System.out.println("古いシステム");
    }
}

class Adapter implements NewSystem {
    OldSystem oldSystem;

    public Adapter(OldSystem oldSystem) {
        this.oldSystem = oldSystem;
    }

    public void newMethod() {
        oldSystem.oldMethod();
    }

    public static void main(String[] args) {
        OldSystem old = new OldSystem();
        NewSystem newObj = new Adapter(old);
        newObj.newMethod(); // "古いシステム" と出力される
    }
}

このコードでは、NewSystem interfaceがクライアントが期待する新しいAPIを定義しています。

OldSystemクラスは既存のシステムで、newMethodではなくoldMethodを提供しています。

AdapterクラスはNewSystem interfaceを実装し、oldMethodを内部で呼び出しています。

実行結果として「古いシステム」と表示されます。

○サンプルコード6:strategyパターンの利用

Strategyパターンは、アルゴリズムをオブジェクトで表現し、それを簡単に切り替えることができる設計パターンです。

interfaceを用いて様々なアルゴリズムを定義し、その実装を切り替えることができます。

// Strategyパターンの例
interface SortingStrategy {
    int[] sort(int[] numbers);
}

class BubbleSort implements SortingStrategy {
    public int[] sort(int[] numbers) {
        // バブルソートの処理
        return numbers;
    }
}

class QuickSort implements SortingStrategy {
    public int[] sort(int[] numbers) {
        // クイックソートの処理
        return numbers;
    }
}

public class SortingClient {
    private SortingStrategy strategy;

    public void setStrategy(SortingStrategy strategy) {
        this.strategy = strategy;
    }

    public int[] performSort(int[] numbers) {
        return strategy.sort(numbers);
    }

    public static void main(String[] args) {
        SortingClient client = new SortingClient();
        client.setStrategy(new BubbleSort());
        int[] result = client.performSort(new int[]{3, 1, 4, 1, 5, 9});
        // ここでresultはバブルソートによって整列される
    }
}

このサンプルコードでは、SortingStrategy interfaceを作成し、異なるソートアルゴリズム(ここではBubbleSortQuickSort)をそれぞれのクラスで実装しています。

SortingClientクラスは、実際にソートを行うクラスです。

setStrategyメソッドで具体的なソートアルゴリズムを指定できます。

ここではバブルソートを指定して、整列された配列を得ることができます。

○サンプルコード7:observerパターンの応用

Observer(観察者)パターンは、一つのオブジェクトが状態の変化を他のオブジェクトに通知する際に用いられる設計パターンです。

このパターンは、特にGUIやイベントリスナーなど、一つの出来事が多数のオブジェクトに影響を及ぼす場合に有用です。

Javaのinterfaceを用いてこの設計パターンを実装する方法を見てみましょう。

// ObserverパターンのJavaでの実装
interface Observer {
    void update(String message);
}

class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    public void update(String message) {
        System.out.println(name + "がメッセージを受け取りました: " + message);
    }
}

class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }

    public static void main(String[] args) {
        Subject subject = new Subject();
        Observer observer1 = new ConcreteObserver("Observer1");
        Observer observer2 = new ConcreteObserver("Observer2");

        subject.addObserver(observer1);
        subject.addObserver(observer2);

        subject.notifyObservers("こんにちは");
    }
}

このコードはObserverパターンの一例であり、Observer interfaceを定義しています。

updateメソッドは、観察対象のオブジェクト(この例ではSubjectクラス)が状態の変更を通知するために用います。

ConcreteObserverクラスはObserver interfaceを実装し、独自の処理(ここでは単にコンソールにメッセージを出力)をupdateメソッド内で行っています。

最後にSubjectクラスでは、Observerオブジェクトを登録し、状態変化時にそれら全てに通知を送ります。

このコードを実行すると、”Observer1がメッセージを受け取りました: こんにちは” と “Observer2がメッセージを受け取りました: こんにちは” がコンソールに出力されます。

これは、SubjectクラスのnotifyObserversメソッドが各Observer(具体的にはConcreteObserver)のupdateメソッドを呼び出しているためです。

○サンプルコード8:ラムダ式とともに使う

Java 8以降、ラムダ式とinterfaceを組み合わせることで、よりシンプルなコードが書けます。

特に、一つの抽象メソッドだけを持つinterface(関数型interface)は、ラムダ式で簡潔に表現できます。

// ラムダ式とinterfaceの組み合わせ
@FunctionalInterface
interface Greeting {
    void sayHello(String name);
}

public class LambdaExample {
    public static void main(String[] args) {
        Greeting greeting = name -> System.out.println("こんにちは、" + name);

        greeting.sayHello("世界");
    }
}

こちらのコードでは、@FunctionalInterfaceアノテーションを使って関数型interfaceであることを明示しています。

Greeting interfaceにはsayHelloという一つのメソッドがあります。

メインメソッド内でラムダ式を用いてこのメソッドを実装し、”こんにちは、世界”と出力します。

このコードを実行すると、コンソールに”こんにちは、世界”と表示されます。

これはラムダ式によってsayHelloメソッドが簡潔に実装されたためです。

●Javaのinterfaceの注意点と対処法

interfaceを使用する際には、いくつかの注意点があります。

これらの注意点を理解しておくことで、効率的にコードを書くことができます。

ここでは主にそのようなポイントについて詳しく解説します。

○一つのクラスが複数のinterfaceを実装する際の注意点

Javaでは一つのクラスが複数のinterfaceを実装することが許されています。

しかし、このとき注意しなければならないのは、それぞれのinterfaceが同名のdefaultメソッドを持っている場合です。

同名のdefaultメソッドがあると、どちらのメソッドが優先されるべきかが不明確になってしまいます。

このような状況を避けるためには、該当のクラスで明示的にそのメソッドをオーバーライドする必要があります。

このような場合には、コンパイラがエラーを出力してくれるため、コードの修正が必要になることをすぐに認識できます。

○サンプルコード9:名前の衝突とその対処法

名前の衝突が生じる可能性も考慮に入れて、実際のサンプルコードを見てみましょう。

interface InterfaceA {
    default void show() {
        System.out.println("InterfaceAのshowメソッド");
    }
}

interface InterfaceB {
    default void show() {
        System.out.println("InterfaceBのshowメソッド");
    }
}

class MyClass implements InterfaceA, InterfaceB {
    // 明示的にshowメソッドをオーバーライド
    public void show() {
        InterfaceA.super.show();
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.show(); // Output: InterfaceAのshowメソッド
    }
}

このコード例ではInterfaceAInterfaceBという二つのinterfaceがあります。

両方ともshowというdefaultメソッドを持っています。

MyClassはこれらのinterfaceを両方実装しているため、名前の衝突が発生します。

この問題を解決するために、MyClass内でshowメソッドをオーバーライドしています。

ここではInterfaceAshowメソッドを呼び出しています。

コードを実行すると、”InterfaceAのshowメソッド”というテキストがコンソールに表示されます。

これはMyClass内でInterfaceAshowメソッドが呼び出されるためです。

●interfaceのカスタマイズ方法

Javaのinterfaceは柔軟性が高く、多くのカスタマイズが可能です。カスタマイズすることで、より独自性のあるコードを作成することができます。

ここでは、カスタムアノテーションを使ってinterfaceをどのように拡張できるのかについて解説します。

○サンプルコード10:カスタムアノテーションとinterface

まずは、カスタムアノテーションを作成して、それをinterfaceに適用する基本的なサンプルコードを見てみましょう。

// カスタムアノテーションを定義
@interface CustomAnnotation {
    String value() default "default";
}

// カスタムアノテーションを使用したinterface
@CustomAnnotation(value = "特別なinterface")
public interface CustomizableInterface {
    void doSomething();
}

// interfaceを実装したクラス
public class CustomClass implements CustomizableInterface {
    public void doSomething() {
        System.out.println("何かをします。");
    }

    public static void main(String[] args) {
        CustomClass customClass = new CustomClass();
        customClass.doSomething();  // 何かをします。
    }
}

このコードではまず、CustomAnnotationという名前のカスタムアノテーションを定義しています。

そして、このアノテーションをCustomizableInterfaceというinterfaceに適用しています。

カスタムアノテーションを使ってinterfaceに追加情報を付与することができます。

この場合、”特別なinterface”という値をCustomAnnotationに設定しています。

このコードの実行結果は、”何かをします。”という文字列がコンソールに表示されます。

これはCustomClass内でdoSomethingメソッドが呼び出された結果です。

このように、カスタムアノテーションを使うことでinterfaceのメタデータを豊富にすることができます。

このメタデータはリフレクションを用いることで、実行時に動的に情報を取得することも可能です。

□補足:カスタムアノテーションとリフレクション

カスタムアノテーションとリフレクションを組み合わせることで、実行時にアノテーションの情報を取得して処理を分岐させるなどの高度な操作が可能です。

public class CustomClass implements CustomizableInterface {
    @Override
    public void doSomething() {
        System.out.println("何かをします。");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        CustomClass customClass = new CustomClass();
        customClass.doSomething();  // 何かをします。

        Class<?> clazz = Class.forName("CustomClass");
        CustomAnnotation annotation = clazz.getAnnotation(CustomAnnotation.class);
        if (annotation != null) {
            System.out.println("このクラスにはCustomAnnotationが適用されています: " + annotation.value());
        }
    }
}

この補足のサンプルコードでは、リフレクションを用いてCustomAnnotationの値を取得しています。

もしCustomAnnotationが適用されている場合、その値をコンソールに出力します。

この機能は動的な処理や設定変更に対応する際に非常に有用です。

まとめ

この記事で取り上げたJavaのinterfaceは、多くのプログラミングシナリオで非常に有用な概念です。

interfaceの基本的な定義から応用例、カスタマイズ方法まで、多角的に解説してきました。

初心者から上級者まで、各レベルの開発者がJavaのinterfaceを効率よく活用できるよう、多数のサンプルコードとともに詳細に説明しました。

この記事を通じて、interfaceの基礎から応用、そして高度なカスタマイズまで一通りマスターできたことでしょう。

これからもプログラミングを学習し続ける中で、本記事で学んだ知識が皆さんのコーディングスキルの向上に寄与することを願っています。

最後までお読みいただき、ありがとうございました。