インターフェースの多重実装とは
インターフェースの多重実装は「1つのクラスが複数のインターフェース契約を同時に満たす」ことです。Javaはクラスの多重継承はできませんが、インターフェースは複数まとめて implements できます。これにより、1つのクラスに複数の“役割”を与え、呼び出し側は必要な契約だけを見る疎結合な設計が実現します。
何が嬉しいのか(重要)
複数の役割を合成できるため、機能を柔らかく組み合わせられます。たとえば「ログ出力できる」「実行できる」「閉じられる」などの契約を分離し、クラスは必要なものだけ採用します。呼び出し側は「Logger として」「Runnable として」と文脈ごとに扱えるため、差し替え・テスト・拡張が簡単になります。大規模でも“契約ごとに独立”するので、依存が絡みにくく保守が楽です。
基本構文と最小例
複数インターフェースの同時 implements
インターフェースをカンマ区切りで並べ、すべてのメソッドを実装します。
interface Logger { void info(String msg); }
interface Runner { void run(); }
final class App implements Logger, Runner {
@Override public void info(String msg) { System.out.println("[INFO] " + msg); }
@Override public void run() { info("start"); }
}
JavaApp は Logger と Runner の両方として扱えます。文脈に応じて必要な契約だけ渡すと依存が軽くなります。
例題で理解する役割合成
例 1: 監査可能なタスク
interface Task { void execute(); }
interface Auditable { void audit(String tag); }
final class EmailTask implements Task, Auditable {
@Override public void execute() { System.out.println("send email"); }
@Override public void audit(String tag) { System.out.println("[AUDIT] " + tag); }
}
// 呼び出し側は必要な役割だけに依存
void runTask(Task t) { t.execute(); }
void logAudit(Auditable a) { a.audit("start"); }
Java同じインスタンスを Task としても Auditable としても使えるため、配線が柔らかく保てます。
例 2: 正規化と検証の分離
interface Normalizer { String apply(String s); }
interface Validator { boolean valid(String s); }
final class UserNameRules implements Normalizer, Validator {
@Override public String apply(String s) {
return s == null ? "" : s.trim().replaceAll("\\s+", " ");
}
@Override public boolean valid(String s) {
var t = apply(s);
return !t.isBlank() && t.length() <= 50;
}
}
Java同一のルールセットを「整形器」と「検証器」として使い分け可能。テストでは片方の契約だけモックにすることもできます。
default メソッドの衝突と解決(重要な落とし穴)
複数インターフェースが同名・同シグネチャの default メソッドを持つと衝突します。実装側で「どれを採用するか」または「独自の上書き」を必ず明示してください。
interface A { default String tag() { return "A"; } }
interface B { default String tag() { return "B"; } }
final class C implements A, B {
@Override
public String tag() {
return A.super.tag() + "+" + B.super.tag(); // 明示的に両方を合成
}
}
Java解決のコツは「衝突させない命名」か「実装側で一本化」。重い共通処理は抽象クラスに寄せ、default は軽い補助に留めると衝突が減ります。
抽象クラスとの併用で骨格を固定する
インターフェースは“役割の契約”を与え、抽象クラスは“流れ・不変条件・共通ロジック”を与えます。多重実装で役割を合成しつつ、抽象クラスで安全な骨格を固定すると、柔軟性と安全性のバランスが取れます。
interface Task { void run(); }
interface Logger { void info(String msg); }
abstract class BaseTask implements Task, Logger {
@Override public final void run() {
info("begin");
try { execute(); } finally { info("end"); }
}
protected abstract void execute();
@Override public void info(String msg) { System.out.println("[INFO] " + msg); } // 既定実装
}
final class EmailTask extends BaseTask {
@Override protected void execute() { System.out.println("email"); }
}
Java呼び出し側は Task だけに依存、ログの扱いは骨格で統一。役割合成+骨格固定の定石です。
設計指針(重要な深掘り)
役割は小さく、明確に切る
1つのインターフェースは“単一責務”に絞り、名前・引数・戻り値の意味を明確にします。こうすると多重実装で合成しやすく、テストも容易です。
default は軽く、骨格は抽象クラスへ
複数の default が絡み始めたら、抽象クラスに共通処理を移して衝突を避けます。インターフェースは契約中心に。
注入で依存を緩める
多重実装したからといって常に“1インスタンスで全部”を持つ必要はありません。コンストラクタでそれぞれの役割を別インスタンスとして注入し、合成クラスは委譲でまとめても構いません(アダプタ・デコレータの発想)。
つまずきポイントと回避
契約の曖昧さ
「何を保証するメソッドか」をドキュメントや命名で固定します。前提(null許容、範囲)と例外方針を明記するだけで、差し替えの安全性が上がります。
役割が巨大化する
1インターフェースに多機能を詰め込むと、多重実装の利点が薄れます。分割(インターフェース分離の原則)で小さく保つのが鉄則です。
default のダイヤモンド問題
衝突は実装側で必ず上書き。曖昧さを残すと意味論が崩れます。必要なら A.super.m() のように明示解決します。
仕上げのアドバイス(重要部分のまとめ)
インターフェースの多重実装は「役割を小さく分け、必要なだけ合成して差し替えに強くする」ための武器です。契約は明確に、default は軽く、骨格は抽象クラスに寄せる。衝突は実装側で明示解決し、依存は注入で緩める——この型を守れば、拡張しやすくテストしやすい疎結合設計が自然に手に入ります。

