【2026年7月最新】Javaのinterfaceとは?使い方・実装方法・抽象クラスとの違いをコード例付きで完全解説
「Javaのinterfaceって何のためにあるの?」「implementsとextendsはどう違う?」——Javaを学び始めると、必ずぶつかる疑問です。
interfaceはJavaのオブジェクト指向プログラミングにおける最重要の設計概念の一つです。正しく理解すれば、保守性が高く拡張しやすいコードが書けるようになります。しかし「なんとなく動いている」状態では、チーム開発で大きなトラブルの原因になります。
この記事では、interfaceの基礎概念から実際のコード例、抽象クラスとの違い、defaultメソッド・staticメソッドの使い方、そして多重継承の実現まで、初心者でも迷わないよう段階的に解説します。
この記事を読むとわかること:
- ✓Javaのinterfaceとは何か——その概念と存在意義
- ✓interfaceの書き方とimplementsによる実装方法——コード例付き
- ✓抽象クラス(abstract class)との違い——使い分けの判断基準
- ✓defaultメソッドとstaticメソッドの使い方——Java 8以降の新機能
- ✓複数interfaceの実装(多重継承)——Javaで多重継承を実現する方法
- ✓実践的な設計パターン——現場で使えるinterface活用法
01 WHAT IS INTERFACE Javaのinterfaceとは何か オブジェクト指向設計における「約束事」の仕組みを理解する
interfaceとは、ひとことで言えば「このクラスはこれができますよ」という約束事(契約)を定義するための仕組みです。
たとえば、「飛べる」というinterfaceを定義すれば、鳥・ドローン・飛行機などの異なるクラスが、それぞれ異なる方法で「飛ぶ」を実装できます。呼び出し側は「飛べるもの」として扱えればよく、実際にどんな物体が飛んでいるかは知る必要がありません。これがポリモーフィズム(多態性)の本質です。
📖 用語解説: インターフェース(interface)
Javaにおけるinterfaceとは、クラスが実装すべきメソッドの名前・引数・戻り値型だけを定義した「設計図の設計図」です。interfaceそのものはインスタンスを生成できません。interfaceを実装したクラスが、実際の処理を記述します。Java 8以降はdefaultメソッドとstaticメソッドも持てます。
1-1. interfaceが必要な理由
「メソッドの中身を書かないなら、なぜinterfaceが必要なのか?」という疑問はよくあります。その答えは「呼び出し側と実装側の依存関係を切り離すため」です。
具体例で考えましょう。社内システムに「通知機能」があるとします。現在はメール通知ですが、将来的にはSlack通知やSMS通知に切り替えるかもしれない。このとき、通知方法ごとにメインの処理コードを書き換えるのは危険です。バグが生まれやすく、テストも大変になります。
interfaceを使えば、「通知する」という契約だけを固定して、実装はメール・Slack・SMSで自由に変えられます。メインの処理コードはinterface型で受け取るだけなので、通知方法が変わっても一切修正不要です。
1-2. interfaceとクラスの基本的な違い
| 項目 | interface | 通常のクラス |
|---|---|---|
| インスタンス生成 | 不可 | 可 |
| メソッドの中身 | 原則なし(Java 8以降はdefaultで持てる) | あり |
| フィールド | public static final定数のみ | 自由 |
| 継承 | 複数のinterfaceをimplements可能 | 1クラスのみextends |
| コンストラクタ | なし | あり |
最大の違いは「複数のinterfaceを同時に実装できる」という点です。通常のクラス継承では親クラスは1つだけですが、interfaceは何個でもimplementsできます。これがJavaで多重継承を実現する唯一の方法です(詳細はH2「複数interfaceの実装」で解説)。
02 SYNTAX & IMPLEMENTS 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", "新規注文が入りました");
}
}
03 INTERFACE vs ABSTRACT 抽象クラスとの違いを徹底比較 「どちらを使うべきか」の判断基準を完全整理
Javaを学ぶと必ず悩むのが「interfaceと抽象クラス、どちらを使えばいいの?」という問いです。結論から言えば「is-a関係ならabstract class、can-do関係ならinterface」が基本的な使い分けです。
📖 用語解説: abstract class(抽象クラス)
abstractキーワードで宣言された、インスタンス生成できないクラスのこと。抽象メソッド(処理なし)と通常メソッド(処理あり)の両方を持てます。コンストラクタも持てるため、共通の初期化処理を持たせることができます。サブクラスはextendsで継承し、抽象メソッドをオーバーライドして実装します。1クラスしか継承できない点がinterfaceとの大きな違いです。
3-1. 詳細比較表
| 比較項目 | interface | abstract class |
|---|---|---|
| インスタンス生成 | 不可 | 不可 |
| 通常メソッドの実装 | 不可(defaultメソッドは例外) | 可 |
| 抽象メソッド | 全メソッドが抽象(defaultを除く) | 一部または全部が抽象 |
| フィールド | public static final定数のみ | 自由(private/protected等も可) |
| コンストラクタ | なし | あり |
| 多重実装/継承 | 複数implementsで多重実装OK | extendsは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の主流です。
04 DEFAULT METHOD 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";
}
}
05 STATIC METHOD 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の使用 | 可(自インスタンスを参照) | 不可 |
06 MULTIPLE INTERFACES 複数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");
}
}
07 DESIGN PATTERNS interfaceの実践的な設計パターン 現場で使えるinterfaceの設計テクニック
interfaceの基礎を理解したら、次は実際の開発現場で使われる設計パターンを学びましょう。ここでは特に重要な3つのパターンを紹介します。
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()を呼べば各クラスの実装が動く——これがポリモーフィズムです。
08 COMMON MISTAKES よくある質問と間違い 初心者がはまりやすいポイントを総まとめ
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は特定の場面に限られるからです。
よくある質問
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メソッドは呼び出せない点は通常クラスと同じです。
Javaのinterfaceを理解したら、次はAIを使ってJava開発の生産性を10倍にする方法を学びませんか? AI鬼管理では、Claude Codeを活用して業務コードの自動生成・レビュー・リファクタリングを実践するノウハウを提供しています。
NEXT STEP
この記事の内容を、あなたのビジネスで
実践してみませんか?
AI活用を自社で回せるようになりたい方へ
AI鬼管理
Claude Code・Cowork導入支援から業務設計・社内浸透まで実践ベースで伴走。「自社で回せる組織」を90日で作る経営者向けトレーニング。
Claude Codeで業務自動化を90日で叩き込む
経営者向けの伴走型パーソナルトレーニング
Claude Code を業務に落とし込む
専門研修コース一覧
受講者本人の業務を題材に、「使いこなせる」状態になるまで伴走する研修プログラム。1対1特化型・ハンズオン・法人講座の3コースを展開中。業務特化・実装まで踏み込むタイプのClaude Code研修です。
研修コース一覧を見る →AI鬼管理へのお問い合わせ
この記事を読んで気になった方へ。
AI鬼管理の専門スタッフが、御社に最適な
業務自動化プランを無料でご提案します。




