中級 65分 Lesson 5

インターフェース・関数型インターフェース・ラムダ式・enum

interface の全メンバー種別・default 競合ルール・@FunctionalInterface・ラムダ省略規則・メソッド参照4種・enum 完全解説

Java Java Silver SE21 インターフェース ラムダ式 enum 関数型インターフェース

Chapter 05 ─ インターフェース・関数型インターフェース・ラムダ式・enum

Silver 試験の頻出トップ3に入るテーマ。「インターフェースのメンバーに何が書けるか」「default メソッドの競合をどう解決するか」「ラムダの省略ルール」「関数型インターフェースの判定」が毎回出る。enum は近年出題が増加中。


5-1. インターフェースとは何か ─「契約書」の比喩

継承(Chapter 04)は「血縁関係」だった。インターフェースは**「契約関係」**。

現実の比喩: 就職の雇用契約書を想像してほしい。「あなたは毎週月曜に報告書を提出する義務がある」と書いてある。雇用される人が営業マンでもエンジニアでも管理職でも、この義務は共通。インターフェースはこの契約書にあたる。

「Printable(印刷できる)」契約
  → Printer が実装 → レーザープリンターで印刷
  → PdfWriter が実装 → PDF として保存
  → FaxMachine が実装 → 電話回線で送信

3つのクラスは互いに関係がない(継承関係もない)が、同じ「Printable」型として扱える。

public interface Printable {
    void print();  // 「print() を実装する義務がある」という契約だけ。中身なし。
}
Printable p = new Printer();    // Printer は Printable 契約を履行している
p.print();                      // → "レーザーで印刷"

p = new PdfWriter();            // PdfWriter に差し替え
p.print();                      // → "PDF として保存"
// 使う側のコードは 1 行も変わらない!

インターフェースの本質的な価値: 実装クラスを差し替えても、使う側のコードが変わらない「プラグイン構造」を作れる。


5-2. インターフェースのメンバー ─ 何が書けて何が書けないか

インターフェースに書けるメンバーは Java バージョンによって増えてきた。試験は SE 21 なので全部把握しておく。

フィールド ─ 必ず定数(public static final

public interface Config {
    int MAX_RETRY = 3;               // 実は: public static final int MAX_RETRY = 3;
    String DEFAULT_HOST = "localhost"; // 実は: public static final String DEFAULT_HOST = ...
}

書かなくても public static final が自動的に付く。だから:

interface Config {
    int MAX = 10;           // OK
    // int count;           // コンパイルエラー: 初期化が必要(final だから)
    // private int x = 5;  // コンパイルエラー: private フィールドは書けない
}

試験の罠: 「インターフェースにインスタンス変数(状態)を持たせることができる」→ ×(できない)。すべて static final で定数のみ。

抽象メソッド ─ 実装クラスへの「義務」

public interface Shape {
    double area();      // 実は: public abstract double area();
    double perimeter(); // 実は: public abstract double perimeter();
}

public abstract は省略してもコンパイラが補う。でも private abstractprotected abstract はインターフェースでは書けない。インターフェースのメソッドは public か(Java 9 以降は)private のみ。

interface Foo {
    void methodA();            // OK (public abstract)
    public void methodB();     // OK
    abstract void methodC();   // OK
    // protected void methodD(); // コンパイルエラー!
}

default メソッド ─ デフォルト実装付きメソッド(Java 8+)

public interface Flyable {
    void fly();  // 抽象メソッド(必須実装)

    default void land() {
        System.out.println("着陸する(デフォルト動作)");
    }
    // 実装クラスはオーバーライドしなくてもよい。でもしてもよい。
}

なぜ default メソッドが生まれたか(背景):
Java 8 でコレクション API に forEachstream などを後付けしたかった。でもインターフェースに新しいメソッドを追加すると、それを実装している既存の全クラスが一斉にコンパイルエラーになる。何千もの OSS ライブラリが壊れる。それを防ぐために「デフォルト実装あり」のメソッドを追加できる仕組みが default メソッド。後方互換性のための発明。

// Java 8 で List インターフェースに追加された default メソッドの例
List<String> list = Arrays.asList("b", "a", "c");
list.forEach(System.out::println); // forEach は default メソッド
list.sort(Comparator.naturalOrder()); // sort も default メソッド

static メソッド ─ インターフェース直属のユーティリティ(Java 8+)

public interface MathUtils {
    static int square(int n) {
        return n * n;
    }
}

// 呼び出し: 必ずインターフェース名で呼ぶ
int result = MathUtils.square(5);  // 25

// 実装クラス経由では呼べない(継承されない)
MyImpl obj = new MyImpl();
// obj.square(5);  // コンパイルエラー!static メソッドは継承されない

これは Math.abs() と同じイメージ。インターフェースに関連するユーティリティ関数を、そのインターフェース自体に持たせられる。

private メソッド ─ default/static の共通ロジックを抽出(Java 9+)

public interface Logger {
    default void logInfo(String msg) {
        log("INFO", msg);   // private メソッドを呼ぶ
    }

    default void logError(String msg) {
        log("ERROR", msg);  // 共通ロジックを再利用
    }

    private void log(String level, String msg) {
        System.out.println("[" + level + "] " + msg);
    }
}

private メソッドは default/static メソッドの重複コードを切り出すためだけに使う。実装クラスからは見えない。

まとめ表

メンバー種類Java自動付与省略できるか継承されるかオーバーライド
フィールド1.0public static final初期化は必須不可(定数)
抽象メソッド1.0public abstract○(実装義務)必須
default メソッド8public-任意
static メソッド8public-不可
private メソッド9なし-不可

5-3. インターフェースの実装 ─ implements

interface Flyable { void fly(); }
interface Swimmable { void swim(); }

// クラスは複数のインターフェースを同時に実装できる
class Duck implements Flyable, Swimmable {
    @Override public void fly()  { System.out.println("羽ばたく"); }
    @Override public void swim() { System.out.println("水を掻く"); }
}

重要な制約:

  • インターフェースの抽象メソッドをひとつでも実装しないクラスは abstract にしなければならない
  • クラスは extends 1つ + implements 複数が可能
// OK: 継承と多重実装の組み合わせ
class Amphibian extends Animal implements Flyable, Swimmable { ... }

// NG: class は 2 つ extends できない
// class Frog extends Animal, Reptile { ... }  // コンパイルエラー!

インターフェース型の変数

Flyable f = new Duck();   // Duck は Flyable を実装している → OK
f.fly();                  // OK(Flyable が持つメソッド)
// f.swim();              // コンパイルエラー!Flyable には swim() がない

// ダウンキャストすれば Swimmable として使える
Swimmable s = (Swimmable) f;  // Duck は Swimmable も実装しているので安全
s.swim();                      // OK

5-4. インターフェースの継承 ─ extends(複数可)

インターフェースはインターフェースを extends で継承できる。複数可。

interface Animal    { void breathe(); }
interface Domestic  { void bond();    }

// インターフェース同士は extends(複数可)
interface Pet extends Animal, Domestic {
    void beg();  // 新しいメソッドを追加
}

// Pet を implements するクラスは 3 つ全部実装が必要
class Cat implements Pet {
    @Override public void breathe() { System.out.println("呼吸"); }
    @Override public void bond()    { System.out.println("なつく"); }
    @Override public void beg()     { System.out.println("おねだり"); }
}

5-5. 抽象クラス vs インターフェース ─ 試験最頻出比較

これは毎回と言っていいほど出題される。完璧に覚えること。

比較項目抽象クラスインターフェース
インスタンス化不可不可
キーワードabstract classinterface
継承・実装extends1つだけimplements複数可
親の数1つ複数
コンストラクタありなし
フィールド何でも(インスタンス変数OK)定数のみpublic static final
メソッドの修飾子何でも(private, protected 等)publicprivate(Java 9+)
メソッドの種類抽象・具体 なんでも抽象・defaultstaticprivate
主な用途is-a 関係(Dog is an Animal)can-do 関係(Duck can Fly)

「どちらを使うか」の実践判断:

  • 共通の「状態(フィールド)」を持つなら → 抽象クラス
  • 「能力」を複数の無関係なクラスに付与したいなら → インターフェース
  • 既存の継承階層に能力を追加したいなら → インターフェースextends は 1 つまでなので)

試験でよくある引っかけ: 「インターフェースにコンストラクタを書いた」「インターフェースのフィールドに private を付けた」「インターフェースのメソッドに protected を付けた」→ すべてコンパイルエラー。


5-6. default メソッドの競合 ─ 3 つの優先順位ルール

複数の親から同名・同シグネチャの default メソッドを引き継ぐと競合が起きる。解決ルールは 3 段階。

ルール 1: クラスのメソッドが最優先

class Base {
    public void hello() { System.out.println("Base"); }
}
interface Greeter {
    default void hello() { System.out.println("Greeter"); }
}

class Child extends Base implements Greeter {
    // オーバーライドしなくてよい → Base.hello() が使われる
}

new Child().hello();  // → "Base"(クラスが勝つ)

理由: 具体的な実装(クラス)がデフォルト実装(インターフェース)より常に優先。

ルール 2: サブインターフェースが優先

interface A {
    default void hello() { System.out.println("A"); }
}
interface B extends A {
    default void hello() { System.out.println("B"); }  // A を上書き
}

class C implements A, B {
    // B は A のサブインターフェース → B.hello() が優先
}

new C().hello();  // → "B"

理由: より特化した(サブの)インターフェースの定義が優先される。

ルール 3: ルール 1・2 で決まらなければコンパイルエラー → 明示的に解決

interface A { default void hello() { System.out.println("A"); } }
interface B { default void hello() { System.out.println("B"); } }

// A と B は互いに継承関係なし → どちらも同等 → 競合!
// class C implements A, B { }  // コンパイルエラー!

// 解決策: 実装クラスでオーバーライド必須
class C implements A, B {
    @Override
    public void hello() {
        A.super.hello();  // A の default を明示的に呼ぶ
        // または B.super.hello();
        // または独自実装を書く
    }
}

A.super.hello() という特殊構文で「A インターフェースの default メソッドを呼ぶ」と指定できる。super.hello() だけでは「どちらの super か」が不明なのでエラー。

優先順位まとめ:

  1. クラス(extends した具体クラス) > default
  2. サブインターフェース > 親インターフェース
  3. 同等の競合 → コンパイルエラー → オーバーライドで解決

5-7. 関数型インターフェース ─ ラムダの入れ物

定義

抽象メソッドがちょうど 1 つだけのインターフェースを「関数型インターフェース」と呼ぶ。

@FunctionalInterface
public interface Converter {
    int convert(String s);  // 抽象メソッドが 1 つ → 関数型インターフェース
}

@FunctionalInterface省略できるが、書いておくと「抽象メソッドが 2 つ以上になったらコンパイルエラーにしてくれ」とコンパイラに依頼できる。間違いを早期発見できる。

重要な落とし穴: 「抽象メソッドが 1 つ」の条件は以下のものをカウントしない:

@FunctionalInterface
interface MyFunc {
    String process(String s);  // 抽象: これだけ → 関数型インターフェース

    default void log() {       // default メソッド: カウントしない(実装あり)
        System.out.println("ログ");
    }

    static MyFunc identity() { // static メソッド: カウントしない
        return s -> s;
    }

    boolean equals(Object o);  // Object のメソッドの再宣言: カウントしない
    String toString();         // Object のメソッドの再宣言: カウントしない
}

Object クラスのメソッド(equals, hashCode, toString 等)を抽象として宣言してもカウントされない。なぜなら、すべてのクラスは Object を継承しているので、どんな実装クラスも必ず持っているため。

// 関数型インターフェース: ○
@FunctionalInterface
interface FI1 { void run(); }                    // 抽象1

// 関数型インターフェース: ○(Object メソッドはカウント外)
@FunctionalInterface
interface FI2 { void run(); boolean equals(Object o); }  // 実質抽象1

// 関数型インターフェース: ✗(コンパイルエラー)
@FunctionalInterface
// interface FI3 { void a(); void b(); }         // 抽象が2つ

// 関数型インターフェース: ✗(抽象0)
// @FunctionalInterface
// interface FI4 { }                             // @FunctionalInterface が付けばエラー

java.util.function パッケージ ─ 覚える6種

Java 8 で追加された標準の関数型インターフェース群。自分で定義しなくてもこれで十分なことが多い。

インターフェース抽象メソッド入力出力用途の覚え方
Predicate<T>boolean test(T t)Tboolean述語」条件判定・フィルタ
Function<T,R>R apply(T t)TR変換」T を R に変える
Consumer<T>void accept(T t)Tなし消費」受け取るが返さない
Supplier<T>T get()なしT供給」何もなしで生成
UnaryOperator<T>T apply(T t)TTFunction<T,T> の別名
BiFunction<T,U,R>R apply(T t, U u)T, UR2引数バージョンの Function

暗記のコツ(語呂合わせ):

  • Predicate → Pass/Fail(true/false を返す)
  • Function → Form change(形を変える)
  • Consumer → Consume(消費するだけ、返さない)
  • Supplier → Supply(供給する、もらわずに渡す)

Predicatedefault メソッド(試験頻出)

Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven     = n -> n % 2 == 0;

// and: 両方が true のとき true(短絡評価)
System.out.println(isPositive.and(isEven).test(4));  // true(正かつ偶数)
System.out.println(isPositive.and(isEven).test(3));  // false(正だが奇数)

// or: どちらかが true のとき true(短絡評価)
System.out.println(isPositive.or(isEven).test(-2));  // true(負だが偶数)

// negate: 結果を反転
System.out.println(isPositive.negate().test(-5));    // true(正でないので true)

口コミ: 「negate() のスペルを nagate() と書いてコンパイルエラーになった」「not() と混同した」という声が多い。Predicate.not(isPositive) は Java 11+ の static メソッド。negate() はインスタンスメソッド。

Function の合成

Function<String, Integer> toLength = s -> s.length();     // String → int
Function<Integer, String> toLabel  = n -> "長さ:" + n;  // int → String

// andThen: 左→右の順に合成
Function<String, String> composed = toLength.andThen(toLabel);
System.out.println(composed.apply("Hello"));  // "長さ:5"

// compose: 右→左の順(引数の Function を先に実行)
Function<String, String> composed2 = toLabel.compose(toLength);
System.out.println(composed2.apply("Hello")); // "長さ:5"(同じ結果)

andThen は「それからこれ」(左→右)。compose は「これの前にそれ」(右→左)。


5-8. ラムダ式 ─ 匿名クラスの簡略表記

ラムダ式の本質

ラムダ式は「関数型インターフェースの実装を1行で書く糖衣構文(シンタックスシュガー)」。内部的には匿名クラスと(ほぼ)等価。

// ── 匿名クラスで書く場合(Java 7 以前のスタイル)──
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("走る");
    }
};

// ── ラムダ式(Java 8 以降)──
Runnable r2 = () -> System.out.println("走る");

// 動作は同じ
r1.run();  // "走る"
r2.run();  // "走る"

コードが劇的に短くなる。関数型インターフェースであれば何でもラムダで書ける。

ラムダ式の構文

( 引数リスト ) -> 本体

本体の書き方は 2 種類:

  • (セミコロンなし): 式の評価結果が戻り値
  • { 文リスト; }: 明示的な return(戻り値がある場合)が必要
// 最も冗長な形(完全形)
Function<String, Integer> f1 = (String s) -> { return s.length(); };

// 段階的に省略していく
Function<String, Integer> f2 = (String s) -> s.length();   // {} と return 省略
Function<String, Integer> f3 = (s) -> s.length();          // 型名を省略
Function<String, Integer> f4 = s -> s.length();            // () も省略(引数1つ)

省略ルール ─ 試験で頻出(落とし穴だらけ)

1. 引数の ()

引数の数() の省略
0 個省略不可(必須)() -> "hello"
1 個省略s -> s.length()
2 個以上省略不可(a, b) -> a + b
Supplier<String> s1 = () -> "hello";   // OK: 0引数は () 必須
// Supplier<String> s2 = -> "hello";   // コンパイルエラー!

Function<String, Integer> f1 = s -> s.length();  // OK: 1引数は () 省略可
BiFunction<Integer, Integer, Integer> b1 = (a, b) -> a + b;  // OK: 2引数は () 必須

2. 引数の型

全部書くか、全部省略するか」。型は混在できない。

BiFunction<String, Integer, String> f;

f = (String s, Integer n) -> s.repeat(n);  // OK: 全部書く
f = (s, n) -> s.repeat(n);                // OK: 全部省略
// f = (String s, n) -> s.repeat(n);      // コンパイルエラー!型の混在
// f = (s, Integer n) -> s.repeat(n);     // コンパイルエラー!型の混在

型を書く場合は var を使うことも可能(SE 11+):

f = (var s, var n) -> s.repeat(n);  // OK(型推論)
// f = (var s, n) -> s.repeat(n);   // コンパイルエラー!var と非 var の混在

3. {}return

セットで省略するか、セットで使うか」。どちらか片方だけは不可。

Function<Integer, Integer> f;

f = n -> n * n;               // OK: {} も return も省略(式形式)
f = n -> { return n * n; };  // OK: {} あり、return も書く

// f = n -> { n * n; };       // コンパイルエラー!{} あって return なし
// f = n -> return n * n;     // コンパイルエラー!return あって {} なし

// void を返す場合は return なしでもよい
Consumer<String> c = s -> { System.out.println(s); };  // OK: void は return 不要
Consumer<String> c2 = s -> System.out.println(s);      // OK: 式形式

試験の頻出引っかけ: n -> { n * n; }{} があって return がない。式の評価結果が捨てられてコンパイルエラーになる。

effectively final ─ ラムダが参照できるローカル変数

ラムダ式から外側のローカル変数を参照する場合、その変数は final または実質的に final(effectively final) でなければならない。

String greeting = "Hello, ";       // ← 後で変更されていない → effectively final

Consumer<String> greeter = name -> System.out.println(greeting + name);

// greeting = "Hi, ";  // ← これを追加した瞬間、上のラムダがコンパイルエラー
greeter.accept("Alice");  // "Hello, Alice"

「なぜ effectively final が必要か」(原理):
ラムダ式は別スレッドで実行されることがある。もし外側の変数が変更可能なら、ラムダが実行されるタイミングによって値が変わり、予測不能になる。Java はこれを防ぐため、ラムダが参照するローカル変数のコピーを作る。そのコピーが正確であるためには、元の変数が変更されないことが保証されている必要がある。

effective final の判定:

int x = 10;      // ← x を変更する代入がこの後ない → effectively final
int y = 10;
y = 20;          // ← y は変更されている → NOT effectively final

Supplier<Integer> s1 = () -> x;   // OK
// Supplier<Integer> s2 = () -> y; // コンパイルエラー!y は effectively final でない

例外: インスタンス変数・クラス変数(フィールド)は effectively final 制約を受けない。

class Counter {
    int count = 0;  // インスタンス変数

    void run() {
        Runnable r = () -> {
            count++;  // OK: インスタンス変数はラムダ内から変更できる
        };
        r.run();
    }
}

5-9. メソッド参照 ─ ラムダのさらなる省略形

「ラムダの中身が既存メソッドの呼び出しだけ」という場合に使える省略構文がメソッド参照

クラス名::メソッド名
インスタンス変数::メソッド名
クラス名::new

4 種類のメソッド参照

種類 1: 静的メソッド参照 クラス名::staticメソッド名

// ラムダ式
Function<String, Integer> f1 = s -> Integer.parseInt(s);

// メソッド参照(等価)
Function<String, Integer> f2 = Integer::parseInt;

// ラムダの引数が静的メソッドの引数にそのまま渡される
System.out.println(f2.apply("42"));  // 42

種類 2: 特定インスタンスのメソッド参照 オブジェクト::インスタンスメソッド名

String prefix = "Hello, ";

// ラムダ式
Function<String, String> f1 = name -> prefix.concat(name);

// メソッド参照(等価)
Function<String, String> f2 = prefix::concat;

// prefix という特定のインスタンスに紐付いている
System.out.println(f2.apply("Bob"));  // "Hello, Bob"

種類 3: 任意インスタンスのメソッド参照 クラス名::インスタンスメソッド名

// ラムダ式
Function<String, String> f1 = s -> s.toUpperCase();

// メソッド参照(等価)
Function<String, String> f2 = String::toUpperCase;

// ラムダの「第1引数」がレシーバー(.toUpperCase() を呼ばれる側)になる
System.out.println(f2.apply("hello"));  // "HELLO"

種類 2 と種類 3 の違い(試験頻出の混乱ポイント):

  • 種類 2: prefix::concat → 特定の String オブジェクト prefix に対して concat を呼ぶ
  • 種類 3: String::toUpperCase → ラムダの引数(任意の String)に対して toUpperCase を呼ぶ
// 種類 2: 2 引数のラムダ → 特定インスタンスの 1 引数メソッドになる(obj は固定)
Function<String, String> bound = prefix::concat;
// 実際の動き: s -> prefix.concat(s)

// 種類 3: 1 引数のラムダ → 引数自身に対してメソッドを呼ぶ
Function<String, String> unbound = String::toUpperCase;
// 実際の動き: s -> s.toUpperCase()

種類 4: コンストラクタ参照 クラス名::new

// ラムダ式
Supplier<ArrayList<String>> s1 = () -> new ArrayList<>();
Function<Integer, ArrayList<String>> f1 = n -> new ArrayList<>(n);

// コンストラクタ参照(等価)
Supplier<ArrayList<String>> s2 = ArrayList::new;
Function<Integer, ArrayList<String>> f2 = ArrayList::new;

// 関数型インターフェースのシグネチャから「どのコンストラクタか」が推論される

4 種類の比較表

種類構文等価なラムダ
静的メソッドClass::staticMethodx -> Class.staticMethod(x)
特定インスタンスobj::methodx -> obj.method(x)
任意インスタンスClass::instanceMethodx -> x.method()
コンストラクタClass::new() -> new Class()

5-10. enum(列挙型)─ 定数の型安全な集合

なぜこの章に enum があるのか? Java Silver 試験の出題範囲では enum はラムダ・関数型インターフェースと同じドメインに分類されている。また enumimplements でインターフェースを実装できる(この章で学んだ内容が直接つながる)。さらに実際の試験では enum 定数を switch 式やラムダと組み合わせるパターンが出題されるため、まとめて学ぶのが効率的。

enum とは ─「決まった値しか取らない型」

曜日、季節、方角、信号の色など「取りうる値が決まっている」定数の集合を型として表現できる。

// 昔ながらの int 定数(型安全でない)
public static final int MON = 0, TUE = 1, WED = 2;
// 問題: setDay(99) と呼べてしまう → コンパイラが気づけない

// enum を使う(型安全)
public enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
// setDay(Day.MONDAY) → Day 型しか渡せない → コンパイラが守ってくれる

基本的な使い方と便利メソッド

Day today = Day.WEDNESDAY;

System.out.println(today);            // "WEDNESDAY"(toString() が自動で名前を返す)
System.out.println(today.name());     // "WEDNESDAY"(定数名を文字列で返す)
System.out.println(today.ordinal()); // 2(宣言順インデックス、0始まり)

// 文字列から enum を取得: valueOf(大文字小文字厳密一致)
Day fri = Day.valueOf("FRIDAY");     // → Day.FRIDAY
// Day.valueOf("friday");            // IllegalArgumentException(小文字NG)

// すべての値を配列で取得: values()
Day[] all = Day.values();
for (Day d : all) {
    System.out.printf("%d: %s%n", d.ordinal(), d.name());
}
// 0: MONDAY
// 1: TUESDAY
// ...

試験の罠: Day.valueOf("friday")実行時例外IllegalArgumentException)。コンパイルエラーではない。ordinal() は 0 始まり。

switch 式での使用

Day d = Day.SATURDAY;

// switch で enum を使う場合、case には型名不要(変数から推論)
String type = switch (d) {
    case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "平日";
    case SATURDAY, SUNDAY -> "週末";
};
System.out.println(type);  // "週末"

// NG: case Day.SATURDAY → コンパイルエラー(型名は不要)

フィールド・コンストラクタ・メソッドを持つ enum

enum の定数は実はオブジェクト。フィールドとメソッドを持てる。

public enum Planet {
    MERCURY(3.303e+23, 2.4397e6),   // コンストラクタ呼び出し
    VENUS  (4.869e+24, 6.0518e6),
    EARTH  (5.976e+24, 6.37814e6);  // ← 定数リストの末尾に `;` が必要

    private final double mass;    // kg
    private final double radius;  // m

    // enum のコンストラクタは暗黙的に private(外から new はできない)
    Planet(double mass, double radius) {
        this.mass   = mass;
        this.radius = radius;
    }

    public double surfaceGravity() {
        final double G = 6.67300E-11;
        return G * mass / (radius * radius);
    }

    public double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
}

System.out.println(Planet.EARTH.surfaceGravity());  // 約 9.8
System.out.println(Planet.MARS.surfaceWeight(75.0)); // 火星での体重

重要ポイント:

  • enum のコンストラクタは 明示的に private と書かなくても実質 private → 外から new Planet(...) は不可
  • フィールドは final にするのが慣習(定数の性質上)
  • 定数リストの後にフィールド・メソッドを書く場合、定数リスト末尾のセミコロンが必須

enum がインターフェースを implements できる

interface Describable {
    String describe();
}

public enum Season implements Describable {
    SPRING { @Override public String describe() { return "春:花が咲く"; } },
    SUMMER { @Override public String describe() { return "夏:暑い"; } },
    AUTUMN { @Override public String describe() { return "秋:紅葉"; } },
    WINTER { @Override public String describe() { return "冬:寒い"; } };
    // ↑ 各定数に個別実装 {} を書く場合もセミコロンで終わる
}

System.out.println(Season.SPRING.describe());  // "春:花が咲く"

各定数に独自の実装 { } を与えることができる(定数ごとに匿名クラスのような振る舞い)。

enum の比較 ─ == が安全

Day d1 = Day.MONDAY;
Day d2 = Day.valueOf("MONDAY");

System.out.println(d1 == d2);       // true(同じ定数は JVM 内で同一インスタンス)
System.out.println(d1.equals(d2));  // true(equals も動く)

String と違い、enum== で安全に比較できる。enum 定数は JVM 内に 1 つしか存在しないため、参照比較で確実に同一性を確認できる。

compareTo と自然順序

enumComparable<T> を実装しており、compareTo()ordinal() の差を返す。

System.out.println(Day.WEDNESDAY.compareTo(Day.MONDAY));  // 2 - 0 = 2
System.out.println(Day.MONDAY.compareTo(Day.FRIDAY));     // 0 - 4 = -4

enum は Comparable を実装しているが、Comparable を再実装(上書き)はできない


✏️ 練習問題

問題 1: コンパイル可否

次のコードはコンパイルできるか。エラーなら理由を答えよ。

@FunctionalInterface
interface MyInterface {
    void execute();
    default void log() { System.out.println("log"); }
    boolean equals(Object o);
    static MyInterface noop() { return () -> {}; }
}
答え

コンパイルできる。 MyInterface は関数型インターフェース。

  • execute(): 抽象メソッド 1 つ ✓
  • log(): default メソッド → カウントしない
  • equals(Object o): Object のメソッドの再宣言 → カウントしない
  • static noop(): static メソッド → カウントしない

抽象メソッドが実質 1 つなので @FunctionalInterface 条件を満たす。

問題 2: ラムダの省略ルール

次のうちコンパイルエラーになるものをすべて選べ。

// A
Runnable r = () -> System.out.println("run");

// B
Function<Integer, Integer> f = x -> return x * 2;

// C
BiFunction<String, String, Integer> g = (s1, String s2) -> s1.compareTo(s2);

// D
Supplier<String> s = () -> "hello";
答え

B と C がコンパイルエラー。

  • A: OK。0 引数なので () は必須、本体は式形式。
  • B: エラー。{} なしに return は使えない。正しくは x -> x * 2 または x -> { return x * 2; }
  • C: エラー。引数の型の混在(s1 は型なし、s2String あり)。全部書くか全部省略。
  • D: OK。0 引数の Supplier() 省略不可だがきちんと書いてある。

問題 3: default メソッドの競合

次のコードの出力を答えよ。

interface X {
    default void greet() { System.out.println("X"); }
}
interface Y extends X {
    default void greet() { System.out.println("Y"); }
}
class Base {
    public void greet() { System.out.println("Base"); }
}
class Z extends Base implements X, Y {
    // オーバーライドなし
}
new Z().greet();
答え

Base が出力される。

優先順位: クラスのメソッド > サブインターフェース > 親インターフェース。
Base.greet() は具体クラスのメソッドなので、インターフェースの default より常に優先される。

問題 4: enum の動作

enum Color { RED, GREEN, BLUE }

System.out.println(Color.GREEN.ordinal());
System.out.println(Color.valueOf("BLUE").name());
System.out.println(Color.RED.compareTo(Color.BLUE));
答え
1
BLUE
-2
  • GREEN.ordinal() → 1(0始まりで 2 番目)
  • valueOf("BLUE").name() → “BLUE”
  • RED.compareTo(BLUE) → 0 - 2 = -2

Chapter 05 チェックリスト

  • インターフェースのフィールドは public static final(定数のみ、インスタンス変数不可)
  • インターフェースの抽象メソッドは public abstract 自動付与、protected は不可
  • default メソッド(Java 8+): 実装あり、継承される、オーバーライド任意
  • static メソッド(Java 8+): 実装あり、継承されない、インターフェース名で呼ぶ
  • private メソッド(Java 9+): default/static の共通ロジック抽出用
  • インターフェースにコンストラクタは書けない(コンパイルエラー)
  • implements は複数可、extends(クラスの)は 1 つのみ
  • default 競合の優先順位: クラス > サブインターフェース > 同等(→ コンパイルエラー)
  • A.super.hello() で特定インターフェースの default を呼び出せる
  • 関数型インターフェース = 抽象メソッドが 1 つだけ(default/static/Object メソッドはカウント外)
  • @FunctionalInterface はコンパイル時チェックのアノテーション(省略可)
  • 標準 6 種(Predicate/Function/Consumer/Supplier/UnaryOperator/BiFunction)のシグネチャを言える
  • Predicateand()/or()/negate() を使える(negate のスペル注意)
  • ラムダ省略ルール: 0 引数の () は省略不可、型は全部または全部省略、{}return はセット
  • effectively final: ラムダが参照するローカル変数は変更不可。インスタンス変数は制約なし
  • メソッド参照の 4 種類: 静的・特定インスタンス・任意インスタンス・コンストラクタ
  • 種類 2 と種類 3 の違い(obj::method vs Class::method)
  • enum: name()/ordinal()/valueOf()(大小区別)/values() を使える
  • enumvalueOf に存在しない名前を渡すと IllegalArgumentException(実行時)
  • enum コンストラクタは実質 private(外から new 不可)
  • enum 定数リストにフィールドを持たせる場合、定数リスト末尾に ; が必要
  • enum== で安全に比較できる(同一インスタンス保証)
  • enumimplements できる(extends は不可)