読み込み中...

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

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

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

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

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

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

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

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

はじめに

Javaのinterfaceは、クラスに実装すべき振る舞いを約束させるための仕組みです。初心者がつまずきやすいのは、classとの違いよりも、implementsした後に何を実装する必要があるのかを追いにくい点にあります。

そのため、interfaceは「共通の呼び出し口を作るもの」と考えると整理しやすくなります。Javaの使い方を学ぶ段階では、publicな抽象メソッド、defaultメソッド、staticメソッド、privateメソッドを順に押さえると、応用やカスタマイズへ進みやすいです。

動作確認環境
  • Java SE 21 / OpenJDK 21
  • 標準ライブラリのみ使用
📖 この記事で学べること
  • Javaのinterfaceを定義し、クラスへ実装する基本形
  • 複数interface、default、static、privateメソッドの使い分け
  • Adapter、Strategy、Observer、ラムダ式への応用
  • 名前衝突、継承関係、設計上の注意点
  • アノテーションとリフレクションを使ったカスタマイズ

公式ドキュメントの仕様確認には、Oracle Java Tutorialsのinterface解説と、言語仕様を確認できるJava Language Specification 9章が役立ちます。配列やコレクションの基礎を補いたい場合は、内部リンクのJava List型完全ガイドも合わせて確認すると理解がつながります。

項目主な構文使いどころ注意点
基本interfaceinterface / implements共通メソッドを約束する実装クラスは抽象メソッドを実装する
複数実装implements A, B複数の役割を持たせるdefaultメソッド名の衝突に注意する
defaultメソッドdefault void method()既存実装へ影響を抑えて機能追加するクラス側で上書きできる
staticメソッドstaticinterfaceに関連する補助処理を置く実装クラスのインスタンスからは呼び出さない
privateメソッドprivatedefault処理の重複をまとめるJava 9以降で使える
関数型interface@FunctionalInterfaceラムダ式で処理を渡す抽象メソッドは1個にする
AdapterAdapter implements Target既存APIを期待される形へ合わせる変換責務を増やしすぎない
StrategysetStrategy()処理アルゴリズムを切り替えるnull対策を入れる
Observerupdate()状態変化を複数の相手へ通知する登録解除や例外処理を考える
アノテーション@interfaceメタデータを付与する@Retentionの設定を忘れない

Javaのinterfaceとは

Javaのinterfaceは、クラスが提供すべきメソッドの形を定める型です。interface内に宣言した抽象メソッドは、実装クラス側で具体的な処理を持たせる必要があります。

これにより、呼び出し側は実装クラスの詳細を知らなくても、同じメソッド名で処理を扱えます。たとえばAnimal型として受け取った値にmakeSound()を呼べるなら、実体がDogでもCatでも同じ形で扱えますし、ここがポイントです。

interfaceの基本的な概念

interfaceは「このメソッドを持つ」という契約を表します。Javaではextendsによるクラス継承が単一継承である一方、implementsによるinterface実装は複数指定できます。

その特徴により、継承階層とは別に「保存できる」「移動できる」「通知を受け取れる」といった役割を型として表現できるのが目安です。初心者は、interfaceを親クラスの代替ではなく、役割を表すための型と理解すると混乱しにくいでしょう。

具体的には、voidStringintbooleanなどの戻り値や引数を使ってメソッドの形を決めます。実装側では@Overrideを付けると、メソッド名や引数の間違いをコンパイラに検出させやすくなります。

💡 Tips: interface名は、役割が伝わる名前にすると読みやすくなるのがポイントです。ReadableRunnableCloseableのように「できること」を名前に含める設計がよく使われます。

interfaceの歴史と背景

Javaの初期からinterfaceは存在し、抽象化と多態性を支える仕組みとして使われてきました。Java 8ではdefaultメソッドとstaticメソッドが追加され、Java 9ではinterface内のprivateメソッドも利用できるようになっています。

こうした拡張により、古い実装クラスを壊さずにinterfaceへ処理を追加しやすくなりました。一方で、処理を書ける範囲が広がったため、抽象化の境界を曖昧にしない注意点も増えているのが一般的です。

一般に、interfaceには外部から見える契約を置き、状態を多く持つ処理や複雑な制御はクラスへ寄せる設計が扱いやすいです。応用を考える前に、まず契約と実装の分離を押さえると、サンプルコードの意図を読み取りやすくなります。

Javaでのinterfaceの使い方

Javaでのinterfaceの使い方は、定義、実装、呼び出しの流れで整理できます。最小構成ではinterface Animalで契約を作り、class Dog implements Animalで実装し、mainからインスタンスを呼び出するのが現実的です。

その流れが分かると、複数のinterface、defaultstaticprivateへ自然に広げられます。サンプルコードごとに、どの部分が契約で、どの部分が実装なのかを追うのが理解の近道です。

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

基本形では、interface側にmakeSound()だけを宣言し、実装クラスのDogで処理を補います。この形を覚えると、Javaの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が契約、Dogが実装という関係になります。makeSound()を実装しない場合、Dogを通常の具象クラスとしてコンパイルできません。

そのため、interfaceは「呼び出せるメソッドを保証する型」として働きます。System.out.println()の処理内容はDog側に閉じているため、呼び出し側は犬の鳴き声の実装詳細を意識せずに済みますが、これは押さえたい点です。

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

Javaでは、クラスが複数のinterfaceを同時に実装できます。鳴く役割と移動する役割を分けると、AnimalMovableを別々に再利用できます。

// 鳴き声を出力する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();
    }
}

結果: 期待される出力は、1行目が「ワンワン」、2行目が「走る」です。

この使い方では、DogAnimal型としてもMovable型としても扱えます。つまり、処理の受け取り側は必要な役割だけを型で要求できます。

一方、実装するinterfaceが増えすぎるとクラスの責務が広がりますし、これが一つの目安です。初心者は「そのクラスが本当にその役割を持つのか」を確認しながら追加すると、注意点を避けやすくなります。

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

defaultメソッドは、interface内に標準実装を置くための構文です。既存の実装クラスを壊さずにメソッドを追加したい場面で使われます。

defaultメソッド

// 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..."と出力
    }
}

結果: 期待される出力は、1行目が「ワンワン」、2行目が「Zzz…」です。

このコードでは、Dogsleep()を実装していなくても、Animalの標準実装を利用できます。ただし、標準実装が全クラスに合うとは限らないため、必要に応じてDog側で上書きします。

staticメソッド

staticメソッドは、interface名から直接呼び出す補助処理に向きますが、覚えておくと役立つでしょう。インスタンスの状態に依存しない処理をまとめると、関連するロジックの置き場所が明確になります。

// 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(); // "走る"と出力
    }
}

結果: 期待される出力は、1行目が「ワンワン」、2行目が「走る」です。

このサンプルコードでは、Animal.run()という形でstaticメソッドを呼び出します。myDog.run()のような呼び出しではなく、interface名を使う点が通常のインスタンスメソッドとの違いです。

⚠️ 注意: defaultメソッドは継承されますが、staticメソッドは実装クラスへ継承される通常メソッドではありません。呼び出し方を混同すると、コンパイルエラーの原因になります。

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

Java 9以降では、interface内にprivateメソッドを定義できると理解できます。複数のdefaultメソッドから共通処理を呼びたい場合に、重複を減らせます。

// 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..."と出力
    }
}

結果: 期待される出力は、1行目が「ワンワン」、2行目が「Animal: Zzz…」です。

この例では、sleep()が内部でlog(String message)を呼び出します。privateメソッドはinterfaceの外から呼べないため、公開したい契約と内部処理を分けられます。

ただし、interfaceに処理を書き込みすぎると、抽象化の意図が見えにくくなると覚えるとよいでしょう。共通処理が大きい場合は、抽象クラスや通常クラスへの分離も検討するとよいでしょう。

interfaceの応用例

interfaceの応用では、設計パターンと組み合わせて「差し替えられる構造」を作ります。Adapter、Strategy、Observer、ラムダ式は、Javaのinterfaceが持つ多態性を理解するうえで代表的な題材です。

その目的は、処理の詳細を呼び出し側から隠し、必要なメソッドだけを型として見せることにあります。カスタマイズしやすい設計を目指す場合も、この考え方が土台になると考えられます。

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

Adapterパターンは、既存クラスの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が呼び出し側の期待する形を表します。AdapternewMethod()を受け取り、内部でOldSystem.oldMethod()へ処理を渡します。

そのため、呼び出し側はOldSystemの存在を意識しません。既存資産を保ちながら新しいinterfaceへ合わせたい場面で使いやすい応用です。

サンプルコード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の実装に応じてperformSort()の処理が切り替わることです。

この例では、SortingClientが具体的なソート処理を直接持ちません。setStrategy()BubbleSortQuickSortを渡すことで、同じ呼び出し口から別の処理を使えます。

一方、strategynullのままperformSort()を呼ぶと例外につながります。実用コードではコンストラクタで必須にする、またはObjects.requireNonNull()で明示的に検査する対処が考えられますし、ここがポイントです。

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

Observerパターンは、状態変化を複数の受信者へ通知する設計です。イベント通知や画面更新のように、発生元と受信側を分けたい場面でinterfaceが役立ちます。

import java.util.ArrayList;
import java.util.List;

// 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("こんにちは");
    }
}

結果: 期待される出力は「Observer1がメッセージを受け取りました: こんにちは」と「Observer2がメッセージを受け取りました: こんにちは」の2行です。

このコードでは、SubjectList<Observer>で通知先を保持します。notifyObservers()が各Observerupdate()を呼ぶため、通知先を後から追加できます。

ただし、通知先の処理で例外が発生すると、後続の通知に影響する可能性があると言えるでしょう。応用する場合は、登録解除のremoveObserver()や例外の扱いも設計に含めるとよいでしょう。

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

Java 8以降では、抽象メソッドを1個だけ持つinterfaceをラムダ式で実装できます。@FunctionalInterfaceを付けると、条件を満たさない変更が入ったときにコンパイル時に検出できます。

// ラムダ式と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("世界");
    }
}

結果: 期待される出力は「こんにちは、世界」です。

このサンプルコードでは、GreetingsayHello(String name)をラムダ式で実装しています。短い処理を一時的に渡したい場合、匿名クラスより読みやすい形になりやすいです。

同様に、標準ライブラリにはPredicate<T>Function<T, R>Consumer<T>Supplier<T>などの関数型interfaceがあります。既存の型で表せる処理なら、自作する前に標準型を検討すると設計が簡潔になるのが基本です。

関数型interfaceやアノテーション周辺を広げたい場合は、Javaアノテーションの12選が関連します。文字列内の表現を扱う処理では、Javaエスケープ処理の10ステップマスターガイドも補助になります。

Javaのinterfaceの注意点と対処法

Javaのinterfaceの注意点は、主に名前衝突、責務の広げすぎ、バージョン差による構文対応にあるのが目安です。特にdefaultメソッドを複数のinterfaceが持つ場合、実装クラス側で明示的な対処が必要になります。

その対処を知っておくと、初心者でもコンパイルエラーの原因を追いやすくなります。エラーを避けるには、@OverrideInterfaceA.super.show()、責務を分けた小さなinterface設計を組み合わせますが、これは押さえたい点です。

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

複数実装では、同じ名前と同じ引数を持つdefaultメソッドが衝突する場合があります。Javaはどちらの標準実装を使うべきか自動判断しないため、実装クラスで上書きしなければなりません。

このとき、どちらか一方の処理を利用するならInterfaceA.super.method()のように呼び出します。両方の処理を組み合わせる設計も可能ですが、呼び出し順や副作用を明確にする必要があるのがポイントです。

一方、抽象メソッド同士が同じシグネチャであれば、実装は1個で済みます。戻り値や例外宣言の互換性が崩れると別の問題になるため、複数の役割を混ぜる前にメソッド名と契約を確認すると安全です。

ℹ️ 補足: defaultメソッドは既存interfaceの拡張を助けますが、業務ロジックを大量に置く場所ではありません。契約を表す型としての読みやすさを保つことが大切です。

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

同名のdefaultメソッドがある場合、実装クラスでshow()を明示します。どのinterface側の処理を採用するかは、InterfaceA.super.show()のように指定できるのが一般的です。

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メソッド
    }
}

結果: 期待される出力は「InterfaceAのshowメソッド」です。

このサンプルコードでは、InterfaceAInterfaceBがどちらもshow()を持ちます。MyClassが両方を実装するため、show()を上書きして衝突を解消しています。

その中でInterfaceA.super.show()を呼ぶため、採用されるのはInterfaceA側の標準実装です。InterfaceB.super.show()へ変えれば、期待される出力もInterfaceB側の文言に変わりますし、これが一つの目安です。

注意点として、衝突の解消を単なるエラー対応で終わらせると、設計意図が残りません。なぜInterfaceAを優先したのか、または両方の処理を組み合わせるのかを、メソッド名やコメントで分かる形にしておくと保守しやすくなります。

interfaceのカスタマイズ方法

interfaceのカスタマイズでは、アノテーションやリフレクションを組み合わせてメタデータを扱えます。たとえば処理対象の分類、生成対象の識別、フレームワーク側の読み取り情報などを@interfaceで表現できるのが現実的です。

ただし、カスタマイズを増やすほど読み取り側のルールも増えます。単に情報を付けるだけでなく、@Retention@TargetClassgetAnnotation()の関係を理解しておく必要があります。

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

カスタムアノテーションをinterfaceへ付けるには、@interfaceで注釈型を定義すると整理できます。実行時に読み取る予定があるなら、@Retention(RetentionPolicy.RUNTIME)を設定します。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// カスタムアノテーションを定義
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface CustomAnnotation {
    String value() default "default";
}

// カスタムアノテーションを使用したinterface
@CustomAnnotation(value = "特別なinterface")
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へ付与していると理解できます。value()には「特別なinterface」という文字列を設定しているため、後からメタデータとして参照できます。

そのままメソッドを呼ぶだけなら、アノテーションは出力に影響しません。応用として、フレームワークや自作処理がCustomAnnotationを読み取り、対象の分類や処理分岐に利用する形が考えられます。

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

リフレクションを使うと、実行時に型情報やアノテーションを読み取れますが、覚えておくと役立つでしょう。interfaceに付けたアノテーションを取得する場合は、実装クラスではなく対象のinterface型を調べるのが分かりやすいです。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface CustomAnnotation {
    String value() default "default";
}

@CustomAnnotation(value = "特別なinterface")
interface CustomizableInterface {
    void doSomething();
}

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

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

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

結果: 期待される出力は「何かをすると覚えるとよいでしょう。」に続き、「このinterfaceにはCustomAnnotationが適用されています: 特別なinterface」です。

この例では、CustomizableInterface.classからgetAnnotation(CustomAnnotation.class)を呼び、注釈の値を取得します。@RetentionRUNTIMEでない場合、実行時に読み取れないため注意が必要です。

一方、リフレクションは型安全性や追跡しやすさの面で通常呼び出しより複雑になります。設定読み取りやフレームワーク連携など、メタデータが設計上必要な範囲に絞って使うのが現実的です。

カスタマイズを広げる場合、Optionalで取得結果を包む、Map<String, Object>へ変換する、またはenumで分類値を制限する設計もあると考えられます。日付条件の判定と組み合わせる処理では、Javaでうるう年を判定する解説のような基礎ロジックも参考になります。

まとめ

Javaのinterfaceは、実装クラスに共通の呼び出し口を与える型です。初心者は、interfaceで契約を作り、implementsで実装し、@Overrideでメソッドを補う流れから理解すると扱いやすくなります。

そのうえで、defaultstaticprivateを使い分けると、既存コードへの影響を抑えながらinterfaceを拡張できると言えるでしょう。ただし、処理を詰め込みすぎると契約の役割がぼやけるため、責務の境界を意識する必要があります。

応用では、Adapter、Strategy、Observer、ラムダ式によって、処理の差し替えや通知の分離を実現できます。カスタマイズでは、@interface@Retention@TargetgetAnnotation()を組み合わせることで、メタデータを使った設計へ広げられますし、ここを基本と考えるとよいでしょう。

注意点として特に押さえたいのは、複数interfaceで同名のdefaultメソッドが衝突するケースです。実装クラスで明示的に上書きし、必要に応じてInterfaceA.super.show()のように呼び出し元を指定すると、意図が読み取りやすくなります。

Javaのオーバーライドとinterfaceの関係を深めたい場合は、Javaでマスターするオーバーライド解説も関連します。サンプルコードを読み替えながら、契約、実装、呼び出しの境界を確認すると、interfaceの使い方がより実務的に整理できるのが基本です。

関連記事

著者: Japanシーモア編集部

Japanシーモアは、Web/IoT/APP/SYS 分野のプログラミング情報を体系的に提供するメディアです。本記事は編集部による執筆とAI支援を組み合わせて制作し、公開前に編集部が校正しています。誤りや改善案がございましたらお問い合わせよりご連絡ください。

※本記事は実在のエンジニア複数名で構成される Japanシーモア編集部が、AI支援を活用して作成・校正・公開しています。