【2026年7月最新】Javaのinterfaceとは?使い方・実装方法・抽象クラスとの違いをコード例付きで完全解説

【2026年7月最新】Javaのinterfaceとは?使い方・実装方法・抽象クラスとの違いをコード例付きで完全解説

「Javaのinterfaceって何のためにあるの?」「implementsとextendsはどう違う?」——Javaを学び始めると、必ずぶつかる疑問です。

interfaceはJavaのオブジェクト指向プログラミングにおける最重要の設計概念の一つです。正しく理解すれば、保守性が高く拡張しやすいコードが書けるようになります。しかし「なんとなく動いている」状態では、チーム開発で大きなトラブルの原因になります。

この記事では、interfaceの基礎概念から実際のコード例、抽象クラスとの違い、defaultメソッド・staticメソッドの使い方、そして多重継承の実現まで、初心者でも迷わないよう段階的に解説します。

代表菅澤 代表菅澤
interfaceを「なんとなく使っている」段階から「設計の道具として使いこなす」段階に上げることが、この記事の目標です。Java未経験の方は最初から、ある程度触ったことがある方はH2「抽象クラスとの違い」から読み始めることをおすすめします。

この記事を読むとわかること:

  • Javaのinterfaceとは何か——その概念と存在意義
  • interfaceの書き方とimplementsによる実装方法——コード例付き
  • 抽象クラス(abstract class)との違い——使い分けの判断基準
  • defaultメソッドとstaticメソッドの使い方——Java 8以降の新機能
  • 複数interfaceの実装(多重継承)——Javaで多重継承を実現する方法
  • 実践的な設計パターン——現場で使えるinterface活用法
Claude Code 完全解説セミナー|経営者・会社役員専用 1on1 60分 無料Claude Codeを経営に活かしたい方へ — AI鬼管理
📌 この記事の結論
【2026年7月最新】Javaのinterfaceとは?使い方・実装方法・抽象クラスとの違いをコード例付きで完全解説
Javaのinterface(インターフェース)の使い方をコード例付きで完全解説。implementsによる実装、defaultメソッド・staticメソッドの使い分け、抽象クラスとの違い、多重継承の実現方法まで初心者向けに丁寧に説明します。

01 Javaのinterfaceとは何か オブジェクト指向設計における「約束事」の仕組みを理解する

interfaceとは、ひとことで言えば「このクラスはこれができますよ」という約束事(契約)を定義するための仕組みです。

たとえば、「飛べる」というinterfaceを定義すれば、鳥・ドローン・飛行機などの異なるクラスが、それぞれ異なる方法で「飛ぶ」を実装できます。呼び出し側は「飛べるもの」として扱えればよく、実際にどんな物体が飛んでいるかは知る必要がありません。これがポリモーフィズム(多態性)の本質です。

📖 用語解説: インターフェース(interface)

Javaにおけるinterfaceとは、クラスが実装すべきメソッドの名前・引数・戻り値型だけを定義した「設計図の設計図」です。interfaceそのものはインスタンスを生成できません。interfaceを実装したクラスが、実際の処理を記述します。Java 8以降はdefaultメソッドとstaticメソッドも持てます。

1-1. interfaceが必要な理由

「メソッドの中身を書かないなら、なぜinterfaceが必要なのか?」という疑問はよくあります。その答えは「呼び出し側と実装側の依存関係を切り離すため」です。

具体例で考えましょう。社内システムに「通知機能」があるとします。現在はメール通知ですが、将来的にはSlack通知やSMS通知に切り替えるかもしれない。このとき、通知方法ごとにメインの処理コードを書き換えるのは危険です。バグが生まれやすく、テストも大変になります。

interfaceを使えば、「通知する」という契約だけを固定して、実装はメール・Slack・SMSで自由に変えられます。メインの処理コードはinterface型で受け取るだけなので、通知方法が変わっても一切修正不要です。

interface定義
実装クラス作成
implements宣言
呼び出し側はinterface型で使う
AI鬼管理山崎 AI鬼管理山崎
interfaceの本質は「何ができるか(What)を定義し、どうやるか(How)は実装クラスに任せる」という分業の仕組みです。この考え方を「依存性の逆転原則(DIP)」と呼びます。初心者のうちは「型として使える約束書」というイメージで十分です。

1-2. interfaceとクラスの基本的な違い

項目interface通常のクラス
インスタンス生成不可
メソッドの中身原則なし(Java 8以降はdefaultで持てる)あり
フィールドpublic static final定数のみ自由
継承複数のinterfaceをimplements可能1クラスのみextends
コンストラクタなしあり

最大の違いは「複数のinterfaceを同時に実装できる」という点です。通常のクラス継承では親クラスは1つだけですが、interfaceは何個でもimplementsできます。これがJavaで多重継承を実現する唯一の方法です(詳細はH2「複数interfaceの実装」で解説)。

02 interfaceの基本的な書き方と実装 interface定義からimplementsまで、コードで完全理解する

interfaceの構文は非常にシンプルです。interfaceキーワードを使って定義し、実装クラスではimplementsキーワードで適用します。

2-1. interfaceの定義

まず、最もシンプルなinterfaceの定義を見てみましょう。「通知できる」という契約を表すNotifiableインターフェースを定義します。

// Notifiable.java
public interface Notifiable {

    // メソッドのシグネチャのみ(実装は書かない)
    void send(String recipient, String message);

    // 複数のメソッドを定義できる
    boolean isAvailable();

    // 定数を定義できる(暗黙的にpublic static final)
    int MAX_MESSAGE_LENGTH = 1000;
}

ポイントをまとめます:

  • メソッドの中身(処理)は書かない——シグネチャ(名前・引数・戻り値型)だけ
  • アクセス修飾子はすべて暗黙的にpublic——省略してもpublicとして扱われる
  • フィールドはpublic static final定数のみ——値を変えられないクラス定数
  • interfaceの中のメソッドはabstractと同じ——実装は持たない

📖 用語解説: implements

クラスがinterfaceを実装することを宣言するJavaのキーワード。「このクラスはinterfaceで定義された全メソッドを実装する」という約束を意味します。implementsを宣言したクラスがinterface内のメソッドを実装しないとコンパイルエラーになります。複数のinterfaceを実装する場合はカンマ区切りで並べます。

2-2. implementsによる実装

定義したinterfaceを実際のクラスで実装してみましょう。メール通知クラスとSlack通知クラスの2つを作ります。

// EmailNotifier.java
public class EmailNotifier implements Notifiable {

    private String smtpServer;

    public EmailNotifier(String smtpServer) {
        this.smtpServer = smtpServer;
    }

    // interfaceのメソッドを実装(@Overrideアノテーションを付けるのが推奨)
    @Override
    public void send(String recipient, String message) {
        if (message.length() > MAX_MESSAGE_LENGTH) {
            throw new IllegalArgumentException("メッセージが長すぎます");
        }
        System.out.println("[Email] " + recipient + "へ送信: " + message);
        // 実際にはsmtpServerを使ってメール送信する処理を書く
    }

    @Override
    public boolean isAvailable() {
        // SMTPサーバーへの接続確認ロジック(ここでは簡略化)
        return smtpServer != null && !smtpServer.isEmpty();
    }
}
// SlackNotifier.java
public class SlackNotifier implements Notifiable {

    private String webhookUrl;

    public SlackNotifier(String webhookUrl) {
        this.webhookUrl = webhookUrl;
    }

    @Override
    public void send(String recipient, String message) {
        System.out.println("[Slack] チャンネル " + recipient + "へ投稿: " + message);
        // 実際にはWebhook URLへHTTPリクエストを送る処理を書く
    }

    @Override
    public boolean isAvailable() {
        return webhookUrl != null && !webhookUrl.isEmpty();
    }
}

次に、呼び出し側のコードを見てみましょう。ここがinterfaceの真価を発揮する場面です。

// NotificationService.java(呼び出し側)
public class NotificationService {

    // interface型で受け取る——EmailNotifierでもSlackNotifierでも使える
    private Notifiable notifier;

    public NotificationService(Notifiable notifier) {
        this.notifier = notifier;
    }

    public void notifyUser(String userId, String message) {
        if (notifier.isAvailable()) {
            notifier.send(userId, message);
        } else {
            System.out.println("通知サービスが利用不可能です");
        }
    }
}

// Main.java
public class Main {
    public static void main(String[] args) {

        // メール通知を使う場合
        Notifiable emailNotifier = new EmailNotifier("smtp.example.com");
        NotificationService service1 = new NotificationService(emailNotifier);
        service1.notifyUser("user@example.com", "ご注文を承りました");

        // Slack通知に切り替える場合——NotificationServiceのコードは一切変更不要
        Notifiable slackNotifier = new SlackNotifier("https://hooks.slack.com/xxx");
        NotificationService service2 = new NotificationService(slackNotifier);
        service2.notifyUser("#orders", "新規注文が入りました");
    }
}
代表菅澤 代表菅澤
「NotificationServiceのコードは一切変更不要」——ここが大事です。メール通知からSlack通知に切り替えたとき、NotificationServiceを修正しなくていい。interfaceを使うと、変更の影響範囲が最小化されます。これが「疎結合」な設計です。

03 抽象クラスとの違いを徹底比較 「どちらを使うべきか」の判断基準を完全整理

Javaを学ぶと必ず悩むのが「interfaceと抽象クラス、どちらを使えばいいの?」という問いです。結論から言えば「is-a関係ならabstract class、can-do関係ならinterface」が基本的な使い分けです。

設計の判断
is-a関係?(継承)
→ abstract class
can-do関係?(能力)
→ interface

📖 用語解説: abstract class(抽象クラス)

abstractキーワードで宣言された、インスタンス生成できないクラスのこと。抽象メソッド(処理なし)と通常メソッド(処理あり)の両方を持てます。コンストラクタも持てるため、共通の初期化処理を持たせることができます。サブクラスはextendsで継承し、抽象メソッドをオーバーライドして実装します。1クラスしか継承できない点がinterfaceとの大きな違いです。

3-1. 詳細比較表

比較項目interfaceabstract class
インスタンス生成不可不可
通常メソッドの実装不可(defaultメソッドは例外)
抽象メソッド全メソッドが抽象(defaultを除く)一部または全部が抽象
フィールドpublic static final定数のみ自由(private/protected等も可)
コンストラクタなしあり
多重実装/継承複数implementsで多重実装OKextendsは1クラスのみ
アクセス修飾子暗黙的にpublic自由に設定可
主な用途能力・契約の定義共通の基盤クラスの定義

3-2. コードで比較する

「動物」を例に、interfaceと抽象クラスの使い分けを見てみましょう。

// 抽象クラス: 動物という「種類」の共通基盤
// 「動物である(is-a)」という継承関係
public abstract class Animal {

    protected String name;
    protected int age;

    // コンストラクタ(抽象クラスは持てる)
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 通常メソッド(共通実装)
    public void breathe() {
        System.out.println(name + "は呼吸している");
    }

    // 抽象メソッド(各サブクラスで実装必須)
    public abstract void makeSound();

    public String getName() { return name; }
    public int getAge() { return age; }
}

// interface: 「飛べる能力(can-do)」の契約
// 犬も飛行機も同じinterfaceを実装できる
public interface Flyable {
    void fly();
    int getMaxAltitude();
}

// interface: 「泳げる能力(can-do)」の契約
public interface Swimmable {
    void swim();
    double getMaxSpeed();
}

// Dogクラス: Animalを継承(is-a)
public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    @Override
    public void makeSound() {
        System.out.println(name + ":ワンワン!");
    }
}

// Duckクラス: Animalを継承しつつ、飛べて泳げるinterfaceも実装
// → interfaceのおかげで多重「能力」を持てる
public class Duck extends Animal implements Flyable, Swimmable {

    public Duck(String name, int age) {
        super(name, age);
    }

    @Override
    public void makeSound() {
        System.out.println(name + ":ガーガー!");
    }

    @Override
    public void fly() {
        System.out.println(name + "が飛んでいます");
    }

    @Override
    public int getMaxAltitude() { return 500; }

    @Override
    public void swim() {
        System.out.println(name + "が泳いでいます");
    }

    @Override
    public double getMaxSpeed() { return 3.5; }
}

💡 使い分けの判断フロー

① 「AはBの一種だ」と言える → abstract class(例:犬は動物の一種)。② 「AはBができる」と言える → interface(例:アヒルは飛べる、泳げる)。③ 共通の初期化処理やフィールドが必要 → abstract class。④ 複数の異なるクラスに同じ能力を持たせたい → interface。迷ったらinterfaceを選ぶのが現代Javaの主流です。

AI鬼管理山崎 AI鬼管理山崎
Java 8以降のdefaultメソッドの登場で「interfaceでも共通処理が書ける」ようになったため、抽象クラスを使う場面はかなり減りました。「コンストラクタとprotectedフィールドが必要な場合だけabstract classを使う」と覚えておくと迷いが減ります。
Claude Code 完全解説セミナー|経営者・会社役員専用 1on1 60分 無料Claude Codeを経営に活かしたい方へ — AI鬼管理

04 defaultメソッドの使い方(Java 8以降) interfaceに実装を持たせる革命的な新機能

Java 8(2014年リリース)から、interfaceにdefaultメソッドという革命的な機能が追加されました。defaultメソッドを使うと、interfaceに「デフォルトの処理」を持たせることができます。

📖 用語解説: defaultメソッド

Java 8以降でinterfaceに追加された機能。defaultキーワードを付けたメソッドには処理を記述でき、実装クラスでオーバーライドしなくても使えます。主な用途は「既存interfaceに新しいメソッドを追加しても既存の実装クラスが壊れないようにする後方互換性の確保」です。

4-1. defaultメソッドの基本構文

// Printable.java
public interface Printable {

    // 通常の抽象メソッド(実装必須)
    String getContent();

    // defaultメソッド(実装クラスでオーバーライド不要)
    default void print() {
        System.out.println("=== 印刷開始 ===");
        System.out.println(getContent());
        System.out.println("=== 印刷終了 ===");
    }

    // defaultメソッドは他のdefaultメソッドを呼べる
    default void printWithLineNumber() {
        System.out.println("=== 行番号付き印刷 ===");
        String[] lines = getContent().split("\n");
        for (int i = 0; i < lines.length; i++) {
            System.out.println((i + 1) + ": " + lines[i]);
        }
    }
}

// SimpleDocument.java
public class SimpleDocument implements Printable {

    private String text;

    public SimpleDocument(String text) {
        this.text = text;
    }

    // 抽象メソッドは実装必須
    @Override
    public String getContent() {
        return text;
    }

    // defaultメソッドはオーバーライドしなくてもOK
    // (必要ならオーバーライドして上書きできる)
}

// HtmlDocument.java(defaultメソッドをオーバーライドする例)
public class HtmlDocument implements Printable {

    private String html;

    public HtmlDocument(String html) {
        this.html = html;
    }

    @Override
    public String getContent() {
        return html;
    }

    // defaultメソッドをオーバーライドして独自の挙動に変更
    @Override
    public void print() {
        System.out.println("=== HTML印刷(ブラウザレンダリング) ===");
        // HTMLタグを除去して表示
        System.out.println(html.replaceAll("<[^>]+>", ""));
    }
}

// Main.java
public class Main {
    public static void main(String[] args) {

        SimpleDocument doc = new SimpleDocument("Hello\nWorld\nJava");
        doc.print();            // defaultメソッドがそのまま動く
        doc.printWithLineNumber(); // こちらもdefaultメソッド

        HtmlDocument htmlDoc = new HtmlDocument("<h1>タイトル</h1><p>本文</p>");
        htmlDoc.print();        // オーバーライドした独自処理が動く
    }
}

4-2. defaultメソッドが解決する問題

defaultメソッドが追加された最大の理由は「後方互換性の確保」です。既存のinterfaceに新しいメソッドを追加すると、そのinterfaceを実装している全クラスでコンパイルエラーが発生します。大規模プロジェクトでは致命的な問題です。

// 問題のあるケース: 既存interfaceにメソッドを追加したら全実装クラスがエラー
public interface OldService {
    void execute();
    // void newMethod(); // ← これを追加すると既存の実装クラスが全部壊れる!
}

// defaultメソッドで解決: 既存クラスを壊さずに機能追加できる
public interface NewService {
    void execute();

    // defaultで追加すれば既存実装クラスはコンパイルエラーにならない
    default void newMethod() {
        System.out.println("デフォルトの新機能");
    }

    default String getVersion() {
        return "2.0";
    }
}
代表菅澤 代表菅澤
JavaのコレクションフレームワークがJava 8でStream APIをサポートできたのも、defaultメソッドのおかげです。ListやIterableなどの既存interfaceにforEach()などのdefaultメソッドを追加したことで、既存の何千ものサードパーティライブラリを壊さずに機能拡張できました。

05 staticメソッドの使い方 interfaceにユーティリティ機能を持たせる

Java 8以降、interfaceにはstaticメソッドも定義できます。staticメソッドはインスタンスを作らずにinterface名で直接呼び出せる便利な機能です。

📖 用語解説: staticメソッド

staticキーワードを付けたメソッドは、インスタンス生成なしにクラス名(またはinterface名)で直接呼び出せます。interfaceのstaticメソッドは実装クラスに継承されない点が通常のクラスのstaticメソッドと異なります。interface名で明示的に呼び出す必要があります。

5-1. interfaceのstaticメソッドの基本

// Validator.java
public interface Validator<T> {

    // 抽象メソッド(実装必須)
    boolean validate(T value);

    // staticメソッド(ファクトリメソッドとして使うパターン)
    static Validator<String> notEmpty() {
        return value -> value != null && !value.trim().isEmpty();
    }

    static Validator<Integer> positive() {
        return value -> value != null && value > 0;
    }

    static Validator<String> maxLength(int max) {
        return value -> value != null && value.length() <= max;
    }

    // defaultメソッドとの組み合わせ(andでバリデーションを連結)
    default Validator<T> and(Validator<T> other) {
        return value -> this.validate(value) && other.validate(value);
    }
}

// Main.java
public class Main {
    public static void main(String[] args) {

        // staticメソッドはinterface名で直接呼び出す
        Validator<String> notEmpty = Validator.notEmpty();
        Validator<String> maxLen20 = Validator.maxLength(20);

        // defaultメソッドのandで連結
        Validator<String> nameValidator = Validator.notEmpty().and(Validator.maxLength(20));

        System.out.println(nameValidator.validate("山崎"));   // true
        System.out.println(nameValidator.validate(""));       // false(空文字)
        System.out.println(nameValidator.validate("あいうえおかきくけこさしすせそたちつてとなにぬねの")); // false(長すぎ)

        // 整数バリデーション
        Validator<Integer> priceValidator = Validator.positive();
        System.out.println(priceValidator.validate(1000)); // true
        System.out.println(priceValidator.validate(-1));   // false
    }
}

5-2. staticメソッドとdefaultメソッドの使い分け

項目defaultメソッドstaticメソッド
オーバーライド実装クラスで可能不可
継承サブクラスに継承されるされない
呼び出し方インスタンスから呼び出すinterface名で直接呼び出す
主な用途デフォルト実装・後方互換ユーティリティ・ファクトリメソッド
thisの使用可(自インスタンスを参照)不可
AI鬼管理山崎 AI鬼管理山崎
staticメソッドをinterfaceに持たせることで、関連する「ファクトリ(生成)メソッド」や「ユーティリティ(便利関数)」をinterface自体に集約できます。別のクラスを作る必要がなくなるので、コードの見通しがよくなります。

06 複数interfaceの実装(多重継承) Javaで多重継承を安全に実現する方法

Javaはクラスの多重継承(複数の親クラスを持つ)をサポートしていません。これは「ダイアモンド問題」と呼ばれるバグの温床になるためです。しかし、interfaceの複数実装(implements)を使えば、多重継承に相当する設計ができます

📖 用語解説: 多重継承

1つのクラスが複数の親から性質を受け継ぐこと。C++は多重継承をサポートしますが、同名メソッドがどの親クラスのものか曖昧になる「ダイアモンド問題」が発生します。Javaはクラスの多重継承を禁止し、interfaceの複数実装のみを許容することで安全性を確保しています。

6-1. 複数interfaceを実装する基本構文

// Serializable.java(シリアライズ可能)
public interface Serializable {
    String serialize();
    Object deserialize(String data);
}

// Loggable.java(ログ記録可能)
public interface Loggable {
    void log(String message);

    default void logError(String message) {
        log("[ERROR] " + message);
    }

    default void logInfo(String message) {
        log("[INFO] " + message);
    }
}

// Cacheable.java(キャッシュ可能)
public interface Cacheable {
    String getCacheKey();
    int getCacheTtlSeconds();

    default boolean isCacheEnabled() {
        return getCacheTtlSeconds() > 0;
    }
}

// UserRepository.java: 3つのinterfaceを同時に実装
// implements キーワードの後にカンマ区切りで列挙する
public class UserRepository implements Serializable, Loggable, Cacheable {

    private static final int TTL = 300; // 5分

    @Override
    public String serialize() {
        return "{ \"type\": \"UserRepository\" }";
    }

    @Override
    public Object deserialize(String data) {
        // JSON解析処理(実際の実装ではJackson等を使う)
        return new Object();
    }

    @Override
    public void log(String message) {
        System.out.println("[UserRepository] " + message);
    }

    @Override
    public String getCacheKey() {
        return "user:repository:all";
    }

    @Override
    public int getCacheTtlSeconds() {
        return TTL;
    }

    // defaultメソッドはそのまま使える(オーバーライド不要)
    // logError(), logInfo(), isCacheEnabled()がすべて使用可能
}

// Main.java
public class Main {
    public static void main(String[] args) {
        UserRepository repo = new UserRepository();

        repo.logInfo("ユーザーデータを取得します");

        if (repo.isCacheEnabled()) {
            repo.logInfo("キャッシュキー: " + repo.getCacheKey() + " TTL: " + repo.getCacheTtlSeconds() + "秒");
        }

        String data = repo.serialize();
        repo.logInfo("シリアライズ: " + data);
    }
}

⚠️ 同名defaultメソッドの競合に注意

複数のinterfaceを実装するとき、同じメソッド名のdefaultメソッドが複数のinterfaceに存在するとコンパイルエラーになります。この場合は実装クラスで必ずオーバーライドし、「InterfaceName.super.methodName()」の形でどちらのdefaultメソッドを呼ぶか明示する必要があります。

// 競合の解決例
public interface InterfaceA {
    default void hello() { System.out.println("Hello from A"); }
}

public interface InterfaceB {
    default void hello() { System.out.println("Hello from B"); }
}

// コンパイルエラー! → 必ずオーバーライドで解決する
public class MyClass implements InterfaceA, InterfaceB {

    @Override
    public void hello() {
        // どちらかを明示的に呼ぶ
        InterfaceA.super.hello(); // "Hello from A"を呼ぶ場合
        // InterfaceB.super.hello(); // "Hello from B"を呼ぶ場合
        // または独自の処理を書く
        System.out.println("Hello from MyClass");
    }
}
代表菅澤 代表菅澤
複数interfaceを実装するのは非常に強力ですが、「このクラスは何者か」が分かりにくくなるリスクもあります。実装するinterfaceは5個以内を目安にし、クラスが肥大化してきたら分割を検討してください。
Claude Code 完全解説セミナー|経営者・会社役員専用 1on1 60分 無料Claude Codeを経営に活かしたい方へ — AI鬼管理

07 interfaceの実践的な設計パターン 現場で使えるinterfaceの設計テクニック

interfaceの基礎を理解したら、次は実際の開発現場で使われる設計パターンを学びましょう。ここでは特に重要な3つのパターンを紹介します。

Strategy Pattern
Repository Pattern
Command Pattern
疎結合な設計へ

7-1. Strategy Pattern(戦略パターン)

Strategy Patternは、アルゴリズム(処理の戦略)をinterfaceで抽象化し、実行時に切り替えられるようにするパターンです。

// SortStrategy.java(ソートアルゴリズムのinterface)
public interface SortStrategy {
    void sort(int[] array);

    // staticファクトリメソッドで戦略を生成
    static SortStrategy bubbleSort() {
        return array -> {
            int n = array.length;
            for (int i = 0; i < n - 1; i++) {
                for (int j = 0; j < n - i - 1; j++) {
                    if (array[j] > array[j + 1]) {
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                    }
                }
            }
        };
    }

    static SortStrategy quickSort() {
        return array -> java.util.Arrays.sort(array); // 簡略化
    }
}

// Sorter.java(戦略を使う側)
public class Sorter {
    private SortStrategy strategy;

    public Sorter(SortStrategy strategy) {
        this.strategy = strategy;
    }

    // 実行時に戦略を変更できる
    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }

    public int[] sort(int[] array) {
        int[] copy = array.clone();
        strategy.sort(copy);
        return copy;
    }
}

// Main.java
public class Main {
    public static void main(String[] args) {
        int[] data = {5, 2, 8, 1, 9, 3};

        Sorter sorter = new Sorter(SortStrategy.bubbleSort());
        int[] result1 = sorter.sort(data);

        // 実行時に戦略を切り替え
        sorter.setStrategy(SortStrategy.quickSort());
        int[] result2 = sorter.sort(data);
    }
}

7-2. Repository Pattern(リポジトリパターン)

Repository Patternは、データアクセスのロジックをinterfaceで抽象化し、テストしやすく・データソースを変えやすい設計にするパターンです。

// User.java(エンティティ)
public class User {
    private Long id;
    private String name;
    private String email;

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    // getter, setter省略
    public Long getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
}

// UserRepository.java(データアクセスのinterface)
import java.util.List;
import java.util.Optional;

public interface UserRepository {
    Optional<User> findById(Long id);
    List<User> findAll();
    List<User> findByName(String name);
    User save(User user);
    void delete(Long id);
    long count();
}

// MySqlUserRepository.java(本番用MySQL実装)
public class MySqlUserRepository implements UserRepository {
    // MySQLへの実際の接続・クエリ処理

    @Override
    public Optional<User> findById(Long id) {
        // SELECT * FROM users WHERE id = ?
        return Optional.empty(); // 実際はDBから取得
    }

    @Override
    public List<User> findAll() {
        // SELECT * FROM users
        return List.of(); // 実際はDBから取得
    }

    // 他のメソッドも実装...
    @Override public List<User> findByName(String name) { return List.of(); }
    @Override public User save(User user) { return user; }
    @Override public void delete(Long id) {}
    @Override public long count() { return 0; }
}

// InMemoryUserRepository.java(テスト用のメモリ実装)
import java.util.*;

public class InMemoryUserRepository implements UserRepository {
    private final Map<Long, User> store = new HashMap<>();
    private long nextId = 1;

    @Override
    public Optional<User> findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }

    @Override
    public List<User> findAll() {
        return new ArrayList<>(store.values());
    }

    @Override
    public List<User> findByName(String name) {
        List<User> result = new ArrayList<>();
        for (User u : store.values()) {
            if (u.getName().equals(name)) result.add(u);
        }
        return result;
    }

    @Override
    public User save(User user) {
        if (user.getId() == null) {
            User newUser = new User(nextId++, user.getName(), user.getEmail());
            store.put(newUser.getId(), newUser);
            return newUser;
        }
        store.put(user.getId(), user);
        return user;
    }

    @Override
    public void delete(Long id) { store.remove(id); }

    @Override
    public long count() { return store.size(); }
}

// UserService.java(ビジネスロジック側はinterface型で受け取る)
public class UserService {
    private final UserRepository repository;

    // コンストラクタでDI(依存性注入)
    public UserService(UserRepository repository) {
        this.repository = repository;
    }

    public User registerUser(String name, String email) {
        // 重複メールチェック
        List<User> existing = repository.findAll();
        for (User u : existing) {
            if (u.getEmail().equals(email)) {
                throw new IllegalArgumentException("このメールアドレスは既に登録されています");
            }
        }
        return repository.save(new User(null, name, email));
    }
}

💡 Repository Patternとテストの相性

UserServiceのテストを書くとき、MySqlUserRepositoryを使うとDBの準備が必要で面倒です。InMemoryUserRepositoryを使えばDBなしで瞬時にテストを実行できます。interfaceで抽象化しているおかげで、UserServiceのコードは一行も変えずにテスト用の実装に差し替えられます。これが「テストしやすい設計」の核心です。

📖 用語解説: ポリモーフィズム

オブジェクト指向の三大概念の一つ(他はカプセル化・継承)。「多態性」とも訳す。同じインターフェース型の変数が、実際には異なるクラスのインスタンスを指し、呼び出し時に実際のクラスのメソッドが実行される仕組み。Notifiable型の変数にEmailNotifierを入れても、SlackNotifierを入れても、send()を呼べば各クラスの実装が動く——これがポリモーフィズムです。

AI鬼管理山崎 AI鬼管理山崎
Strategy PatternとRepository Patternは現場で最も多く使われるinterfaceの活用パターンです。どちらもinterface型で依存を受け取ることで、実装の差し替えを容易にしています。この設計思想を「DIP(依存性逆転の原則)」と呼び、SOLID原則の一つです。

08 よくある質問と間違い 初心者がはまりやすいポイントを総まとめ

Javaのinterfaceを学ぶ過程で、多くの人が同じ疑問や間違いにぶつかります。ここで一括して整理しておきましょう。

  • Q: interfaceのメソッドにアクセス修飾子を書かないといけない? → 省略すると自動的にpublicになります。privateとprotectedは使えません(Java 9からprivateは使用可能)。
  • Q: interfaceの変数(フィールド)は変更できる? → できません。interfaceのフィールドは暗黙的にpublic static finalなので、定数として扱われます。
  • Q: interfaceをextendsで継承できる? → できます。interfaceがinterfaceを継承する場合はextendsを使います(複数継承も可)。
  • Q: interfaceを実装したクラスがインスタンスを生成できない? → interfaceを実装したクラスは普通にnewできます。インスタンスを生成できないのはinterface自体です。
  • Q: @FunctionalInterfaceとは? → 抽象メソッドが1つだけのinterfaceをFunctionalInterfaceと呼びます。Lambda式(ラムダ式)で実装できます。
  • Q: interfaceに定数を定義するのはよい設計? → 「Constant Interfaceアンチパターン」と呼ばれ、推奨されません。定数は専用のクラス(Enumやfinalクラス)で定義する方がよいです。

特に初心者が混乱しやすいのが「interfaceとabstract classはどちらを先に学ぶべきか」という点ですが、答えはinterfaceを先に学ぶことをおすすめします。現代のJava開発ではinterfaceの使用頻度が圧倒的に高く、abstract classは特定の場面に限られるからです。

代表菅澤 代表菅澤
「よくある間違い」の中でも特に注意してほしいのが「Constant Interfaceアンチパターン」です。interfaceを定数の置き場にするのは、将来的な変更コストを高める設計です。定数はEnumまたはfinalなユーティリティクラスに置く——この習慣を最初から身につけてください。

よくある質問

Q. JavaのinterfaceとCSharpのinterfaceに違いはありますか?

A. 基本的な概念は同じですが、いくつか違いがあります。Java 8以降はdefaultメソッドとstaticメソッドをinterfaceに定義できますが、C#(C# 8.0以前)はできませんでした。C# 8.0以降はdefault interface methodsが追加され、JavaのdefaultメソッドとほぼⅠ同様の機能が使えるようになっています。また、Javaはinterfaceにprivateメソッドを定義できます(Java 9以降)が、C# 8.0以降も同様です。

Q. interfaceのメソッドをすべて実装しないとどうなりますか?

A. コンパイルエラーになります。interfaceのすべての抽象メソッドを実装しないクラスは、自身もabstractクラスとして宣言する必要があります。たとえばimplements後に一部のメソッドしか実装しない場合、abstract class宣言を追加することでコンパイルを通せます。ただしその場合、そのクラス自体もインスタンス生成不可になります。

Q. functionalInterfaceとLambda式はinterfaceと関係ありますか?

A. 深く関係しています。@FunctionalInterfaceアノテーションは「このinterfaceは抽象メソッドが1つだけ」という制約を宣言するもので、Lambda式(匿名関数)を使って簡潔に実装できます。たとえばRunnable(run()が1つだけ)はFunctionalInterfaceで、new Thread(() -> System.out.println("Hello"))のように記述できます。Java 8で導入されたStream APIもFunctionalInterfaceを多用しています。

Q. interfaceを実装したクラスをinterface型で扱うメリットは何ですか?

A. 主なメリットは3つです。①依存関係の疎結合化:呼び出し側は実装クラスを知らなくてよいため、実装を差し替えても呼び出し側を修正する必要がありません。②テストのしやすさ:本番用実装の代わりにテスト用の実装(モック)を渡すことで、外部依存なしのユニットテストが書けます。③ポリモーフィズムの活用:interface型のListに複数の実装クラスのインスタンスを入れて一括処理できます。

Q. Java 9以降でinterfaceに追加された機能はありますか?

A. Java 9でprivateメソッドとprivate staticメソッドがinterfaceに定義できるようになりました。これにより、defaultメソッド同士で共通する処理をprivateメソッドに切り出してコードの重複を避けられます。たとえば複数のdefaultメソッドが同じヘルパー処理を使う場合、そのロジックをprivateメソッドに集約できます。interfaceの外からはprivateメソッドは呼び出せない点は通常クラスと同じです。

AI鬼管理山崎 AI鬼管理山崎
JavaのinterfaceはSpring FrameworkやDagger等の主要フレームワークでもフル活用されています。interfaceをしっかり理解していると、フレームワークの設計思想が読めるようになり、エラーの原因特定も格段に速くなります。まずは今日からコードにinterfaceを1つ追加する練習を始めてみてください。

Javaのinterfaceを理解したら、次はAIを使ってJava開発の生産性を10倍にする方法を学びませんか? AI鬼管理では、Claude Codeを活用して業務コードの自動生成・レビュー・リファクタリングを実践するノウハウを提供しています。

NEXT STEP

この記事の内容を、あなたのビジネスで
実践してみませんか?

AI活用を自社で回せるようになりたい方へ

AI鬼管理

Claude Code・Cowork導入支援から業務設計・社内浸透まで実践ベースで伴走。「自社で回せる組織」を90日で作る経営者向けトレーニング。

AIAI鬼管理

AI鬼管理へのお問い合わせ

この記事を読んで気になった方へ。
AI鬼管理の専門スタッフが、御社に最適な
業務自動化プランを無料でご提案します。

会社名を入力してください
業種を選択してください
お名前を入力してください
正しいメールアドレスを入力してください

1つ以上選択してください
1つ以上選択してください
月額コストを選択してください

約1時間のオンライン面談(Google Meet)です

空き枠を取得中...
面談日時を選択してください

予約確定後、Google Calendarの招待メールをお届けします。
しつこい営業は一切ございません。

監修 最終更新日: 2026年7月19日
菅澤孝平
菅澤 孝平 株式会社GENAI 代表取締役
  • AI業務自動化サービス「AI鬼管理」を運営 — Claude Code を活用し、経営者の業務を「AIエージェントに任せる仕組み」へ転換するパーソナルトレーニングを 伴走構築 で提供。日報・採用・問い合わせ対応・経費精算・議事録・データ集計・営業リスト等の定型業務を、AIに代行させる体制を経営者と一緒に作り込む
  • Claude Code 実装ノウハウを 経営者・法人クライアント に直接指導。生成AIを「便利ツール」ではなく 「業務を任せる存在」 として運用する手法を体系化
  • 「やらせ切る管理」メソッドの開発者。シンゲキ株式会社(2021年設立・鬼管理専門塾運営)にて累計3,000名以上の学習者を志望校合格に導いた管理メソッドを、AI × 経営者支援 に転用
  • 著書『3カ月で志望大学に合格できる鬼管理』(幻冬舎)、『親の過干渉こそ、最強の大学受験対策である。』(講談社)
  • メディア出演: REAL VALUE / カンニング竹山のイチバン研究所 / ええじゃないかBiz 他
  • 明治大学政治経済学部卒
現在は AI鬼管理(Claude Code活用の伴走型パーソナルトレーニング)を主事業とし、経営者と二人三脚で「AIに業務を任せる仕組み」を実装。「実行を強制する環境」を AI で構築する手法を、自社の実運用知見をもとに発信している。