24.

Java インターフェース完全ガイド (default / Functional / Sealed)

編集
この記事の要点
  • インターフェース = メソッドの「契約」のみを定義する型。interface キーワード
  • 実装は implements多重実装可能 (クラスは単一継承のみだがインターフェースは複数 OK)
  • フィールドは自動で public static final、メソッドは自動で public abstract
  • Java 8+ default メソッド / static メソッド、Java 9+ private メソッド
  • Functional Interface (抽象メソッド 1 つ) はラムダ式と相性抜群

インターフェースの基本

// 定義
public interface Animal {
    void sound();        // public abstract が自動付与
    int legs();
}

// 実装
public class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("Wan!");
    }

    @Override
    public int legs() { return 4; }
}

// 多重実装
public interface Swimmer { void swim(); }
public interface Flyer   { void fly();  }

public class Duck implements Animal, Swimmer, Flyer {
    public void sound() { ... }
    public int legs() { return 2; }
    public void swim() { ... }
    public void fly() { ... }
}

暗黙の修飾子

宣言暗黙で付く修飾子
フィールドpublic static final (定数)
抽象メソッドpublic abstract
default メソッド (Java 8+)public
static メソッド (Java 8+)public
ネスト型public static
public interface Config {
    int TIMEOUT = 30;         // public static final が自動付与
    String VERSION = "1.0";   // 同じ

    void load();              // public abstract が自動付与
}

default メソッド (Java 8+)

既存インターフェースに後付けで実装を追加してもサブクラスを壊さないようにするため Java 8 で導入されました。

public interface Logger {
    void log(String msg);

    // ★ default メソッド = デフォルト実装あり
    default void info(String msg)  { log("[INFO] " + msg); }
    default void warn(String msg)  { log("[WARN] " + msg); }
    default void error(String msg) { log("[ERROR] " + msg); }
}

public class ConsoleLogger implements Logger {
    @Override
    public void log(String msg) {
        System.out.println(msg);
    }
    // info / warn / error は実装不要
}

// 必要なら override も可
public class CustomLogger implements Logger {
    @Override public void log(String msg) { ... }
    @Override public void error(String msg) {
        Sentry.capture(msg);
        Logger.super.error(msg);  // 親 default 呼び出し
    }
}

static メソッド (Java 8+)

public interface StringUtils {
    static boolean isEmpty(String s) {
        return s == null || s.isEmpty();
    }

    static String reverse(String s) {
        return new StringBuilder(s).reverse().toString();
    }
}

// 呼び出し方
StringUtils.isEmpty("");        // true
StringUtils.reverse("abc");     // "cba"

// インスタンスからは呼べない
// new SomeClass().isEmpty();  // ❌

private メソッド (Java 9+)

public interface Validator {
    boolean validate(String input);

    default boolean validateAll(List<String> inputs) {
        return inputs.stream().allMatch(this::validate);
    }

    default boolean validateAny(List<String> inputs) {
        return inputs.stream().anyMatch(this::validate);
    }

    // ★ Java 9+ private で内部ヘルパ
    private boolean isNotEmpty(String s) {
        return s != null && !s.isEmpty();
    }
}

Functional Interface とラムダ式

抽象メソッドがちょうど 1 つのインターフェースを Functional Interface と呼びます。@FunctionalInterface アノテーションで強制できます。ラムダ式は Functional Interface に対する糖衣構文です。

@FunctionalInterface
public interface Transformer<T, R> {
    R transform(T input);
}

// ラムダで実装
Transformer<String, Integer> length = s -> s.length();
length.transform("hello");  // 5

// メソッド参照
Transformer<String, Integer> length2 = String::length;

// 標準 Functional Interface (java.util.function)
Function<String, Integer> f = String::length;
Predicate<String> isEmpty = String::isEmpty;
Consumer<String> print = System.out::println;
Supplier<String> get = () -> "hello";
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;

// Stream API はラムダ前提
list.stream()
    .filter(s -> s.length() > 3)
    .map(String::toUpperCase)
    .forEach(System.out::println);

Marker Interface (マーカーインターフェース)

メソッドを持たず、「この型はこの能力を持つ」とマークするだけのインターフェース。代表は Serializable, Cloneable, RandomAccess

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
}

// 現代では Marker より @Annotation を使うことが多い
@MyMarker
public class MyClass { ... }

抽象クラスとの違い

項目インターフェース抽象クラス
キーワードinterface / implementsabstract class / extends
複数継承★ 多重実装 OK単一継承のみ
フィールドpublic static final のみ (定数)インスタンスフィールド可
メソッド抽象 + default + static + private抽象 + 通常メソッド
コンストラクタ不可あり
用途「〜できる」 (能力)「〜は〜である」 (Is-A 関係) + 共通実装

Sealed Interface (Java 17+)

実装できるクラスを限定する Sealed Type が Java 17 で正式リリース:

public sealed interface Shape
    permits Circle, Square, Triangle {}

public final class Circle implements Shape { ... }
public final class Square implements Shape { ... }
public final class Triangle implements Shape { ... }

// パターンマッチ switch (Java 21+) で網羅性チェック
double area(Shape s) {
    return switch (s) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Square sq -> sq.side() * sq.side();
        case Triangle t -> t.base() * t.height() / 2;
        // default 不要 (sealed なのでコンパイラが網羅検証)
    };
}

多重実装で同名 default メソッドが衝突した場合

interface A {
    default String name() { return "A"; }
}

interface B {
    default String name() { return "B"; }
}

// ❌ コンパイルエラー: どちらか不明
class C implements A, B { }

// ✅ 明示的に解決
class C implements A, B {
    @Override
    public String name() {
        return A.super.name() + "+" + B.super.name();
    }
}

FAQ

Q: いつインターフェース、いつ抽象クラス?
A: 「〜できる」「契約」ならインターフェース。「〜は〜である」+ 共通の実装やフィールドを持たせたいなら抽象クラス。

Q: default メソッドを乱用していい?
A: 後付けの拡張には便利だが、新規設計では責務が混じりがち。コア API は抽象メソッドのまま、ユーティリティは static にした方が綺麗。

Q: @FunctionalInterface は必須?
A: 任意。ただし付けると抽象メソッドが 2 つ以上になったときコンパイルエラーになり、契約破壊を防げます。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 基本的なルール
  2. データ型
  3. 変数
  4. 定数
  5. 配列
  6. コレクション(List,Set,Queue)
  7. Map(連想配列)
  8. 演算子
  9. 条件分岐
  10. 繰り返し制御文
  11. クラス
  12. メソッド
  13. インスタンス化
  14. コンストラクタ
  15. staticキーワード
  16. オーバーロード
  17. 継承
  18. オーバーライド
  19. this
  20. super
  21. パッケージ
  22. アクセス修飾子
  23. 抽象クラス・メソッド
  24. インターフェース
  25. カプセル化
  26. データベース接続
  27. セッション
  28. ファイル入出力
  29. ラムダ式

最近更新/作成されたページ