インターフェース・関数型インターフェース・ラムダ式・enum
interface の全メンバー種別・default 競合ルール・@FunctionalInterface・ラムダ省略規則・メソッド参照4種・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 abstract や protected 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 に forEach や stream などを後付けしたかった。でもインターフェースに新しいメソッドを追加すると、それを実装している既存の全クラスが一斉にコンパイルエラーになる。何千もの 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.0 | public static final | 初期化は必須 | ○ | 不可(定数) |
| 抽象メソッド | 1.0 | public abstract | ○ | ○(実装義務) | 必須 |
default メソッド | 8 | public | - | ○ | 任意 |
static メソッド | 8 | public | - | ✗ | 不可 |
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にしなければならない - クラスは
extends1つ +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 class | interface |
| 継承・実装 | extends(1つだけ) | implements(複数可) |
| 親の数 | 1つ | 複数 |
| コンストラクタ | あり | なし |
| フィールド | 何でも(インスタンス変数OK) | 定数のみ(public static final) |
| メソッドの修飾子 | 何でも(private, protected 等) | public か private(Java 9+) |
| メソッドの種類 | 抽象・具体 なんでも | 抽象・default・static・private |
| 主な用途 | 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 か」が不明なのでエラー。
優先順位まとめ:
- クラス(
extendsした具体クラス) > default - サブインターフェース > 親インターフェース
- 同等の競合 → コンパイルエラー → オーバーライドで解決
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) | T | boolean | 「述語」条件判定・フィルタ |
Function<T,R> | R apply(T t) | T | R | 「変換」T を R に変える |
Consumer<T> | void accept(T t) | T | なし | 「消費」受け取るが返さない |
Supplier<T> | T get() | なし | T | 「供給」何もなしで生成 |
UnaryOperator<T> | T apply(T t) | T | T | Function<T,T> の別名 |
BiFunction<T,U,R> | R apply(T t, U u) | T, U | R | 2引数バージョンの Function |
暗記のコツ(語呂合わせ):
- Predicate → Pass/Fail(true/false を返す)
- Function → Form change(形を変える)
- Consumer → Consume(消費するだけ、返さない)
- Supplier → Supply(供給する、もらわずに渡す)
Predicate の default メソッド(試験頻出)
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::staticMethod | x -> Class.staticMethod(x) |
| 特定インスタンス | obj::method | x -> obj.method(x) |
| 任意インスタンス | Class::instanceMethod | x -> x.method() |
| コンストラクタ | Class::new | () -> new Class() |
5-10. enum(列挙型)─ 定数の型安全な集合
なぜこの章に
enumがあるのか? Java Silver 試験の出題範囲ではenumはラムダ・関数型インターフェースと同じドメインに分類されている。またenumはimplementsでインターフェースを実装できる(この章で学んだ内容が直接つながる)。さらに実際の試験では 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 と自然順序
enum は Comparable<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は型なし、s2はStringあり)。全部書くか全部省略。 - 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)のシグネチャを言える
-
Predicateのand()/or()/negate()を使える(negateのスペル注意) - ラムダ省略ルール: 0 引数の
()は省略不可、型は全部または全部省略、{}とreturnはセット -
effectively final: ラムダが参照するローカル変数は変更不可。インスタンス変数は制約なし - メソッド参照の 4 種類: 静的・特定インスタンス・任意インスタンス・コンストラクタ
- 種類 2 と種類 3 の違い(obj::method vs Class::method)
-
enum:name()/ordinal()/valueOf()(大小区別)/values()を使える -
enumのvalueOfに存在しない名前を渡すとIllegalArgumentException(実行時) -
enumコンストラクタは実質private(外からnew不可) -
enum定数リストにフィールドを持たせる場合、定数リスト末尾に;が必要 -
enumは==で安全に比較できる(同一インスタンス保証) -
enumはimplementsできる(extendsは不可)