Javaのインターフェースとは?抽象クラスとの違いと実務での使い方

Mirai

Javaのインターフェースって何なのか全然わからない…クラスで全部書けばよくない?

Zetto

インターフェースは「このメソッドを必ず実装してね」という約束事を定義する仕組みだね。これを使いこなすと、設計が柔軟になって変更に強いコードが書けるようになるんだ。

この記事では、Javaのインターフェースの基本から実務での活用パターンまで解説します。

Zettoのライタープロフィール

本記事の専門性
現役エンジニアのZettoです。Java GoldとJava Silverを保有しており、JavaでのフリーランスWebアプリ開発を複数案件経験しています。

インターフェースの概念を曖昧なまま進めると、Spring Bootなどのフレームワークのコードを読んでも設計の意図がつかめず、読解だけで時間を消費してしまいます。

この記事を読めば、Javaのインターフェースの役割・書き方・抽象クラスとの使い分け・実務での活用パターンまで一通り理解できます。

ぜひ参考にしてみてください。

目次

Javaのインターフェースとは何か

Javaインターフェース概要

まずはインターフェースの基本から押さえておきましょう。

  • インターフェースの役割と仕組み
  • インターフェースを使う3つのメリット
  • クラス・抽象クラスとの大まかな違い

順番に解説していきます。

インターフェースの役割と仕組み

インターフェースとは、「このクラスは必ずこのメソッドを用意してください」という取り決めを定義する仕組みです。

例えるなら、会社の業務規定書のようなイメージです。

「営業担当は必ず見積もり作成・顧客対応・報告の3業務をこなすこと」と書かれていれば、誰が営業担当になっても同じ業務をこなせる状態が保証されます。

Javaのインターフェースも同じで、「このインターフェースを実装するクラスは、必ずこのメソッドを用意してください」という約束事を定義します。

インターフェースの主な特徴

主な特徴は以下の通りです。

  • メソッドの定義だけ書く:処理の中身(実装)は持たない
  • implements したクラスが処理を書く:約束を引き受けたクラスが中身を記述する
  • 複数のインターフェースを同時に受け持てる:1つのクラスで複数の約束事を引き受けられる

「何をするか」だけを定義して、「どうするか」は実装クラスに任せるイメージですね。

Zetto

インターフェースは「設計図の中の約束書」みたいなものです。誰が実装しても同じメソッドが必ず用意されていることを保証できるのが最大の強みですね。

インターフェースを使う3つのメリット

インターフェースを使うメリットは主に3つあります。

  • コードの変更に強くなる:実装クラスを差し替えても、呼び出し側のコードを変えなくて済む
  • チーム開発で設計を統一しやすい:「どのメソッドを用意すべきか」が明示されるので、複数人での開発がスムーズになる
  • テストコードが書きやすくなる:本物の実装クラスをモック(仮の実装)に差し替えやすく、テストを独立して書ける
Zetto

インターフェースを使う場面の意図が理解できると、フレームワークのコード読解も一気に進むようになります。

クラス・抽象クラスとの大まかな違い

インターフェース・通常のクラス・抽象クラスの3つはよく混同されます。

大まかな違いは以下の通りです。

通常のクラス処理の実装を持つ。オブジェクトを直接生成できる
抽象クラス(ちゅうしょうクラス)一部メソッドだけ「実装してね」と定義できる。共通処理も持てる
インターフェース処理の中身を原則持たない。複数を同時に実装できる
Zetto

詳しい比較は後の章で解説するので、ここでは「3種類ある」と把握しておけば十分です。

インターフェースの書き方(宣言とimplements)

インターフェース宣言方法

ここでは実際のコードを使ってインターフェースの書き方を解説します。

  • インターフェースを宣言する基本構文
  • implements キーワードで実装クラスを作る
  • 複数のインターフェースを同時に実装する(多重実装)

順番に見ていきましょう。

インターフェースを宣言する基本構文

インターフェースの宣言は interface キーワードを使います。

public interface Animal {
    void sound(); // メソッドの定義のみ(処理の中身はなし)
    void move();
}

interface の後にインターフェース名を書き、その中にメソッドを定義します。

通常クラスの classinterface に変わるだけで、見た目はほぼ同じです。

ポイントは、メソッドに処理の中身を書かないことです。波括弧 {} は使わず、セミコロンで終わります。

これを「抽象メソッド(ちゅうしょうメソッド)」と呼びます。

インターフェースを宣言するときのルールをまとめると、以下の通りです。

  • メソッドには自動的に public abstract が付く(書かなくてOK)
  • フィールド(変数)は自動的に public static final(定数)になる
  • コンストラクタ(インスタンス生成時に呼ばれる処理)は書けない
Zetto

メソッドに public abstract を省略できるのはJavaの仕様です。書いてもエラーにはなりませんが、現場では省略するのが一般的です。

implementsキーワードで実装クラスを作る

インターフェースを実装するクラスは、implements キーワードを使います。

public class Dog implements Animal {

    @Override
    public void sound() {
        System.out.println("ワンワン!");
    }

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

implements Animal と書くことで、「このクラスは Animal インターフェースの約束を守ります」という宣言になります。

インターフェースで定義したメソッドを全て実装しないとコンパイルエラーになるため、書き忘れを防げるのも大きなメリットです。

@Override アノテーション(注釈)は必須ではありませんが、「インターフェースのメソッドを実装しています」という意図が明示できるので、現場ではつけておくのが一般的です。

Zetto

implements を書いた瞬間に、「このメソッドを全部実装してください」とIDEが教えてくれます。書き忘れをコンパイル時に検出できるので、実装漏れによるバグの予防にもなりますね。

複数のインターフェースを同時に実装する(多重実装)

Javaでは1つのクラスが複数のインターフェースを同時に実装できます。

これを「多重実装(たじゅうじっそう)」と呼びます。

public interface Swimmer {
    void swim();
}

public class Duck implements Animal, Swimmer {

    @Override
    public void sound() {
        System.out.println("ガーガー!");
    }

    @Override
    public void move() {
        System.out.println("歩く");
    }

    @Override
    public void swim() {
        System.out.println("泳ぐ");
    }
}

implements Animal, Swimmer のようにカンマ区切りで複数のインターフェースを並べます。

Javaではクラスの継承(extends)は1つしかできませんが、インターフェースの実装は何個でも重ねられます。

「この能力も持たせたい、あの能力も持たせたい」という場面でとても便利です。

書き方の基本を理解できたと思います。次は抽象クラスとの違いをしっかり整理しましょう。

Zetto

implements を書いた瞬間に、「このメソッドを全部実装してください」とIDEが教えてくれます。書き忘れをコンパイル時に検出できるので、実装漏れによるバグの予防にもなります。

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

抽象クラスとの違い

インターフェースと似た存在として「抽象クラス」があります。

ここでは違いを整理して、使い分けの基準を解説しますね。

  • 抽象クラスとインターフェースの違い一覧
  • インターフェースと抽象クラスの使い分け基準
  • ポリモーフィズムとインターフェースの関係

順に解説します。

抽象クラスとインターフェースの違い一覧

抽象クラスとは、abstract キーワードをつけたクラスで、「実装を強制させたいメソッド」と「共通の処理」の両方を持てるものです。

違いを3つの観点から比較します。

コンストラクタ・フィールドの有無

抽象クラスはコンストラクタとインスタンス変数(オブジェクトごとに異なる値を持つ変数)を持てます。

一方、インターフェースはコンストラクタを持てず、フィールドは定数(public static final)のみです。

状態(データ)を共通で持ちたい場合は、抽象クラスが向いています。

メソッドに実装を書けるか

抽象クラスは抽象メソッド(実装なし)と通常のメソッド(実装あり)の両方を持てます。

インターフェースは原則として実装を持ちませんが、Java 8以降はデフォルトメソッドという形で実装を書けるようになりました。

詳しくは次の章で解説します。

継承・実装の制限

インターフェースは複数を同時に implements できます。

抽象クラスは1つしか extends(継承)できません。

この「多重実装できる」という点がインターフェースの最大の強みです。

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

インターフェースと抽象クラスのどちらを使うか迷ったときのシンプルな基準は次の2つです。

  • 「何ができるか」という能力を定義したい → インターフェース
  • 「何者か」という共通の概念と処理を持ちたい → 抽象クラス

もう少し具体的にすると、「Animal(動物)という共通概念を継承させたい」なら抽象クラス、「Flyable(飛べる)・Swimmable(泳げる)という能力を付与したい」ならインターフェースが向いています。

実務の感覚として、インターフェースを使う場面の方が圧倒的に多いです。Spring Bootを使った開発では、サービス層やリポジトリ層でほぼ必ずインターフェースが登場します。

ポリモーフィズムとインターフェースの関係

ポリモーフィズム(多態性:たたいせい)とは、「同じ命令でもオブジェクトによって動作が変わる」という概念です。

インターフェースはこのポリモーフィズムを実現するのに最適な仕組みです。

Animal animal1 = new Dog();
Animal animal2 = new Cat();

animal1.sound(); // ワンワン!
animal2.sound(); // ニャー!

Animal 型の変数に DogCat を入れても、それぞれの sound() が正しく呼ばれます。

呼び出し側は「Animalsound() を呼ぶ」だけでよく、中身の違いを意識しなくて済みます。

これにより、新しい動物クラスを追加しても呼び出し側のコードを変えなくてよくなります。

Zetto

「何ができるかを定義する → インターフェース」「何者かの共通処理を持つ → 抽象クラス」この一言の基準を頭に入れておくと、設計の場面で迷わなくなりますよ。

現場で通用するインターフェースの使い方

実践的インターフェース活用

基本が押さえられたら、実務でどう使われているかを見ていきましょう。

  • Spring BootのServiceとRepositoryで見るインターフェース設計
  • Java 8以降で追加されたデフォルトメソッドとstaticメソッド
  • テストコードでモックを使うときにインターフェースが活きる理由

それぞれ解説します。

Spring BootのServiceとRepositoryで見るインターフェース設計

Spring Boot(スプリングブート)を使ったWebアプリ開発では、インターフェースが至るところで登場します。

代表的なのが「Service層」と「Repository層」の設計です。

// インターフェース(約束事の定義)
public interface UserService {
    User findById(Long id);
    void save(User user);
}

// 実装クラス(処理の中身)
@Service
public class UserServiceImpl implements UserService {

    @Override
    public User findById(Long id) {
        // DBからユーザーを取得する処理
        return userRepository.findById(id).orElse(null);
    }

    @Override
    public void save(User user) {
        userRepository.save(user);
    }
}

インターフェースと実装クラスを分けることで、「呼び出し側は UserService 型として使えばOK」という状態が作れます。

仮に実装クラスを差し替えても、呼び出し側のコードを変える必要がありません。

僕がJavaのフリーランス案件に入ったとき、このパターンは全ての現場で使われていました。Spring Bootを学ぶなら、このインターフェース設計のパターンは必須の知識です。

Java 8以降で追加されたデフォルトメソッドとstaticメソッド

Java 8から、インターフェースに「デフォルトメソッド」と「staticメソッド」が追加されました。

デフォルトメソッドは、インターフェースに実装を持たせられる機能です。

public interface Greeter {

    // 抽象メソッド(実装クラスで必ず実装する)
    String getName();

    // デフォルトメソッド(実装クラスが上書きしなくてもOK)
    default void greet() {
        System.out.println("こんにちは、" + getName() + "さん!");
    }
}

デフォルトメソッドを使うと、既存の実装クラスに影響を与えずに新しいメソッドを追加できます。

以前はインターフェースにメソッドを1つ追加すると全ての実装クラスにも追加が必要だったので、これは大きな改善でした。

staticメソッドは、インターフェース名で直接呼び出せるメソッドです。

public interface MathUtil {
    static int add(int a, int b) {
        return a + b;
    }
}

// 呼び出し方
int result = MathUtil.add(3, 5); // 8
Zetto

Java 8以降のコードではこの2つが頻繁に登場するので、読み解けるようにしておきましょう。

テストコードでモックを使うときにインターフェースが活きる理由

インターフェースの実用的な強みのひとつが、テストコードでの活用です。

実務では「DBへの接続など外部リソースに依存せずに単体テストを書きたい」場面が多くあります。

このとき、インターフェースを使っていると「モック(仮の実装クラス)」に差し替えることができます。

// テスト用のモック実装
public class MockUserRepository implements UserRepository {

    @Override
    public User findById(Long id) {
        // DBに接続せず、固定のテストデータを返す
        return new User(id, "テストユーザー");
    }
}

呼び出し側は UserRepository 型を受け取るだけなので、本物の実装クラスを渡すかモックを渡すかをテストの状況に応じて選べます。

Mockitoのようなテストフレームワークもインターフェースを前提に設計されており、現場では「インターフェースがないとテストが書きにくい」という場面が少なくありません。

Zetto

インターフェースを使いこなせると、コードの品質が一段上がります。設計力として評価されるスキルです。

Javaのインターフェースの演習問題

演習問題集

理解を深めるために、演習問題を3つ用意しました。

演習問題1

問題

以下の条件を満たす Shape(図形)インターフェースと、それを実装する Circle(円)クラスを作成してください。

  • Shape インターフェースは double getArea() メソッドを持つ
  • Circle クラスは半径(radius)を持ち、getArea() で円の面積を返す
  • 円の面積は Math.PI * radius * radius で計算する

解答例

public interface Shape {
    double getArea();
}

public class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

解説

Shape インターフェースは getArea() の定義だけを持ちます。処理の中身は Circle クラスが担当します。

コンストラクタで半径を受け取り、getArea() 内で面積を計算しています。

@Override をつけることで「インターフェースのメソッドを実装している」と明示できます。

演習問題2

問題

Printable(印刷できる)と Savable(保存できる)の2つのインターフェースを作成し、両方を実装する Document(文書)クラスを作ってください。

  • Printablevoid print() メソッドを持つ
  • Savablevoid save(String fileName) メソッドを持つ
  • Document クラスは両方を実装し、それぞれコンソールにメッセージを表示する

解答例

public interface Printable {
    void print();
}

public interface Savable {
    void save(String fileName);
}

public class Document implements Printable, Savable {

    @Override
    public void print() {
        System.out.println("ドキュメントを印刷します。");
    }

    @Override
    public void save(String fileName) {
        System.out.println(fileName + " に保存します。");
    }
}

解説

implements Printable, Savable とカンマ区切りで2つのインターフェースを実装しています。

これが多重実装です。Document クラスは Printable 型としても Savable 型としても扱えるようになります。

呼び出し側の柔軟性が上がるのを確認してみてください。

演習問題3

問題

デフォルトメソッドを使った練習です。以下の条件を満たす Logger(ログ出力)インターフェースを作成してください。

  • String getPrefix() は抽象メソッド(実装必須)
  • default void log(String message)"[プレフィックス] メッセージ" の形式で出力するデフォルトメソッド
  • InfoLogger クラスを作り、getPrefix()"INFO" を返す

解答例

public interface Logger {
    String getPrefix();

    default void log(String message) {
        System.out.println("[" + getPrefix() + "] " + message);
    }
}

public class InfoLogger implements Logger {

    @Override
    public String getPrefix() {
        return "INFO";
    }
}

// 使い方
InfoLogger logger = new InfoLogger();
logger.log("アプリが起動しました。");
// 出力:[INFO] アプリが起動しました。

解説

InfoLogger クラスは getPrefix() だけを実装すれば、log() メソッドは自動的に使えます。

デフォルトメソッドの中で getPrefix() を呼び出しているため、実装クラスごとに異なるプレフィックスで出力できます。

WarnLoggergetPrefix()"WARN" を返す)など、他のクラスも作って動作を確認してみてください。

Javaインターフェースに関するよくある質問

よくある質問

よくある質問と回答をまとめました。

  • インターフェースに変数を定義したらどうなる?
  • 抽象クラスとインターフェース、どちらを選べばいい?
  • Java 8でインターフェースに実装が書けるようになったのはなぜ?

インターフェースに変数を定義したらどうなる?

インターフェースに変数を定義すると、自動的に public static final(定数)として扱われます。

public interface Config {
    int MAX_SIZE = 100;
    // 自動的に public static final int MAX_SIZE = 100; と同じ意味になる
}

インスタンス変数(オブジェクトごとに異なる値を持つ変数)は定義できません。

値を変えられない定数だけが持てます。状態(変化する値)を持たせたい場合は抽象クラスを使いましょう。

抽象クラスとインターフェース、どちらを選べばいい?

迷ったときのシンプルな判断基準は「状態(フィールド)や共通処理が必要か」です。

  • 共通処理・状態が必要 → 抽象クラス
  • 能力・機能の定義だけ → インターフェース

現場では、インターフェースを使う場面の方が多い傾向があります。

設計の柔軟性を保ちやすいためです。どちらか迷ったら、まずインターフェースで設計してみて、共通処理が増えてきたら抽象クラスへの移行を検討するのがおすすめです。

Java 8でインターフェースに実装が書けるようになったのはなぜ?

Java 8でデフォルトメソッドが追加されたのは、後方互換性(こうほうごかんせい:既存のコードを壊さずに機能を追加できる性質)を保つためです。

それ以前はインターフェースにメソッドを1つ追加するだけで、全ての実装クラスにもそのメソッドを追加しなければなりませんでした。

大規模なライブラリでは、これが非常に大きな変更コストになっていました。

デフォルトメソッドが登場したことで、既存の実装クラスに影響を与えずに新しいメソッドを追加できるようになりました。

java.util.Collection インターフェースに forEach などのメソッドが追加されたのも、このデフォルトメソッドの仕組みのおかげです。

インターフェースの理解が、Javaエンジニアの設計力を底上げする

設計力向上

この記事ではJavaのインターフェースについて、基本から実務での活用パターンまで解説しました。

重要ポイントをまとめると以下の通りです。

  • インターフェースは「約束事」を定義する仕組み:実装を強制させることで設計の一貫性を保てる
  • implements で実装クラスを作る:複数のインターフェースを同時に実装できる(多重実装)
  • 抽象クラスとの使い分けは「状態・共通処理が必要か」で判断:迷ったらまずインターフェースから
  • Spring Bootやテストコードでもフル活用される:現場では必須の知識

インターフェースは最初「なんで使うんだろう」と感じる概念ですが、Spring Bootのコードを読んだりテストコードを書いたりする段階になると、設計の意図が腑に落ちる瞬間があります。

今日学んだ書き方をベースに、まずは演習問題を自分の手で書いて動かしてみてください。

Mirai

インターフェースって最初は難しそうだったけど、Spring Bootとかテストで使う理由がわかると面白いね!

Zetto

Spring Bootを学び始めるとインターフェースがあちこちに出てくるよ。今日の内容が必ず活きてくるから、実際にコードを書いて確かめてみてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次