タイトル: インターフェース
SEOタイトル: Java インターフェース完全ガイド (default / Functional / Sealed)
| この記事の要点 |
|
インターフェースの基本
// 定義
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 inputs) {
return inputs.stream().allMatch(this::validate);
}
default boolean validateAny(List 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 {
R transform(T input);
}
// ラムダで実装
Transformer length = s -> s.length();
length.transform("hello"); // 5
// メソッド参照
Transformer length2 = String::length;
// 標準 Functional Interface (java.util.function)
Function f = String::length;
Predicate isEmpty = String::isEmpty;
Consumer print = System.out::println;
Supplier get = () -> "hello";
BiFunction 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 / implements | abstract 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 つ以上になったときコンパイルエラーになり、契約破壊を防げます。