-- Views
May 30, 26
スライド概要
JJUG CCC 2026 Spring / こうの(虎の穴ラボ) @hk_it7 登壇資料
Ruby/Javaプログラマー。エンジニアリングマネージャー。 軽度の広く浅いオタク。
Enum 徹底入門 Javaの「クラス」としての Enum を使い倒す JJUG CCC 2026 Spring / こうの(虎の穴ラボ)
リンク Twitter 実況補助ツール サンプルコード & スライド HTML https://h-kono-it.github.io/conference-tweet-helper/ https://github.com/h-kono-it/jjug_ccc_2026_spring-enum 実況補助ツールは今すぐ、手元で見たい方は右の QR からどうぞ 2
早速ですが…「定数」の管理、どうしてる? public class OrderService { public static final int STATUS_PENDING = 0; public static final int STATUS_PROCESSING = 1; public static final int STATUS_COMPLETED = 2; public static final int STATUS_FAILED = 3; public void process(int status) { if (status == STATUS_PENDING) { // ... } } } シンプルに見えるが、落とし穴がある 3
どんな問題が起きる? // 定数を「使ってくれる」保証がない service.process(STATUS_PENDING); // ◯ お行儀の良い呼び出し service.process(0); // ✗ 直書きされても止められない service.process(999); // ✗ 範囲外の値もコンパイルは通る // String 版も同じ service.process("pedning"); // ✗ typo がすり抜ける service.process("PENDING"); // ✗ 大文字小文字のゆれも通る 強制力がない — 「定数を使う」のは規約頼み。直書きを防げない 範囲外の値を常に考慮 — テストで「不正値が来たら」のケースが付きまとう 意図が伝わらない — 引数の / だけでは何を渡すべきか分からない int String これを「型」で解決するのが Enum。今日はその活用術を詳しく話します! 4
今日話すこと 1. なぜ Enum か? — 動機とメリット 2. Enum の仕組み — JLS とコンパイル後の姿 3. プロパティと振る舞いを持つ Enum 4. Enum と DB — ORM マッピング 5. Enum の活用パターン — EnumSet/EnumMap・状態遷移・Singleton 6. Enum の進化 — switch 式 7. Enum vs Sealed Class — パターンマッチングとの相性 5
Enumの「端的で強力な」メリット enum OrderStatus { PENDING, PROCESSING, COMPLETED, FAILED } public void process(OrderStatus status) { ... } // 有効な値しか渡せない service.process(OrderStatus.PENDING); // ✓ IDE が補完してくれる service.process(999); // ✗ コンパイルエラー service.process("pedning"); // ✗ コンパイルエラー 定数の意味・有効な範囲が 型として明示 される 6
型で実装を制限し、仕様を明確にする // シグネチャだけで「渡せる値」が伝わる void sendNotification(NotificationType type, Priority priority); order.setStatus( /* ここで補完 */ ) // → IDE も Copilot / Claude も OrderStatus の定数一覧を提示 メソッドシグネチャを見るだけで 何を渡せるか が分かる IDE 補完で有効な値の一覧が即座に出る/AI も型情報を読んで生成 する AI が生成したコードも コンパイラが検証 してくれる 選択肢を増やしたときの対処漏れも検出できる(後述の switch 網羅性) 意図をコードに込めるほど、人間も AI も読みやすいコードになる 7
Enum の仕組み JLS の定義とコンパイル後の姿 8
Enum の仕組み(JLS の定義) Java の Enum は 特殊なクラス が自動的に付与される(明示的には書けない) 各定数は なシングルトンインスタンス コンストラクタは暗黙的に / はコンパイラが自動生成 extends Enum<T> static final private values() valueOf() Season s = Season.SPRING; s.name() // → "SPRING" Season.values() // → Season[] 全定数の配列 Season.valueOf("AUTUMN") // → Season.AUTUMN 9
コンパイル後の姿
// javac が生成するイメージ
public final class Season extends Enum<Season> {
public static final Season SPRING = new Season("SPRING", 0);
public static final Season SUMMER = new Season("SUMMER", 1);
// AUTUMN, WINTER も同様...
private Season(String name, int ordinal) {
super(name, ordinal);
}
// values(), valueOf() も自動生成される
}
クラスは
→ 継承不可
各定数はシングルトン → での比較が安全
final
==
10
プロパティと振る舞いを持つ Enum 11
フィールドを持つ Enum public enum Status { PENDING("pending", "処理待ち"), PROCESSING("processing", "処理中"), COMPLETED("completed", "完了"), FAILED("failed", "失敗"); private final String code; private final String label; Status(String code, String label) { this.code = code; this.label = label; } public String getCode() { return code; } public String getLabel() { return label; } } コンストラクタは暗黙的に private 。フィールドは final にするのが慣習。 12
static ファクトリメソッド
// DB の値(code 文字列)から Enum を逆引き
public static Status fromCode(String code) {
return Arrays.stream(values())
.filter(s -> s.code.equals(code))
.findFirst()
.orElseThrow(() ->
new IllegalArgumentException("不正なコード: " + code));
}
// 見つからない場合は Optional で返す版
public static Optional<Status> findByCode(String code) {
return Arrays.stream(values())
.filter(s -> s.code.equals(code))
.findFirst();
}
valueOf()
は定数名での逆引き。コード値での逆引きは自前で実装する。
13
振る舞いのポリモーフィズム(抽象メソッド) public enum Operation { ADD { @Override public double apply(double x, double y) { return x + y; } }, SUBTRACT { @Override public double apply(double x, double y) { return x - y; } }; // MULTIPLY, DIVIDE も同様... public abstract double apply(double x, double y); } // 使う側は switch 不要 double result = Operation.ADD.apply(3.0, 4.0); // → 7.0 Strategy パターン を Enum の中にコンパクトに閉じ込められる 14
Lambda フィールド(よりコンパクトに)
public enum Operation {
ADD("+",
(x, y) -> x + y),
SUBTRACT("-", (x, y) -> x - y),
MULTIPLY("*", (x, y) -> x * y),
DIVIDE("/",
(x, y) -> x / y),
POWER("^",
Math::pow);
// メソッド参照も OK
private final String symbol;
private final BiFunction<Double, Double, Double> fn;
Operation(String symbol, BiFunction<Double, Double, Double> fn) {
this.symbol = symbol; this.fn = fn;
}
public double apply(double x, double y) { return fn.apply(x, y); }
}
抽象メソッド版より 定義がコンパクト。定数クラス体が不要になる。
15
Enum と DB ORM マッピングのベストプラクティス 16
@Enumerated @Enumerated だけだと足りない理由 は JPA 標準の Enum カラム マッピング指定。 ORDINAL と STRING の2択。 // ORDINAL — 宣言順の番号(0, 1, 2...)をそのまま保存 @Enumerated(EnumType.ORDINAL) private Status status; // → 定数の追加・並び替えで既存データが壊れる // STRING — 定数名("PENDING", "COMPLETED"...)をそのまま保存 @Enumerated(EnumType.STRING) private Status status; // → 定数をリネームすると既存データとズレる Java のコード と DB が密結合 => Java 側を修正しづらくなる コード表現と データ管理を分離する ために、Enum 自身にコード値を持たせる 17
DBにはコード値を持たせる
enum Status {
PENDING("pending"), COMPLETED("completed"), FAILED("failed");
private final String code;
Status(String code) { this.code = code; }
public String getCode() { return code; }
public static Status fromCode(String code) {
return Arrays.stream(values())
.filter(s -> s.code.equals(code))
.findFirst().orElseThrow();
}
}
Java の定数名(
PENDING
)と DB の値(
"pending"
)を 切り離す
18
Converter で橋渡し(JPA)
Spring Data JPA / Hibernate で使える
MyBatis の場合は
で同様のことができる
定数のリネームが DB に影響しない
AttributeConverter
TypeHandler
@Converter(autoApply = true)
class StatusConverter
implements AttributeConverter<Status, String> {
@Override
public String convertToDatabaseColumn(Status s) {
return s.getCode();
// Java → DB
}
@Override
public Status convertToEntityAttribute(String code) {
return Status.fromCode(code); // DB → Java
}
}
19
Enum の活用パターン EnumSet / EnumMap・状態遷移・Singleton 20
集合と判定に便利な EnumSet
enum Permission { READ, WRITE, DELETE, ADMIN }
EnumSet<Permission> userPerms = EnumSet.of(Permission.READ, Permission.WRITE);
EnumSet<Permission> allPerms
= EnumSet.allOf(Permission.class);
EnumSet<Permission> restricted = EnumSet.complementOf(userPerms); // [DELETE, ADMIN]
EnumSet<Day> weekdays = EnumSet.range(Day.MON, Day.FRI);
// 範囲指定
boolean canDelete = userPerms.contains(Permission.DELETE);
// false
内部実装は ビットベクター →
/
/
/
権限・フラグの集合 に便利
of
allOf
complementOf
より高速・省メモリ
など生成 API が豊富
HashSet
range
21
集計に便利な EnumMap
// 注文ステータス別の件数を集計
EnumMap<OrderStatus, Integer> count = new EnumMap<>(OrderStatus.class);
for (OrderStatus s : OrderStatus.values()) count.put(s, 0);
// 全キーを 0 で初期化
orders.forEach(o -> count.merge(o.getStatus(), 1, Integer::sum));
count.forEach((status, n) -> System.out.printf("%-12s: %d件%n", status, n));
// PENDING
: 12件
// PROCESSING
:
// COMPLETED
: 47件
// FAILED
:
3件
0件
← 0 件のステータスも漏れなく出る
全キーを事前初期化 できるので「0件」も含めて確実にレポートできる
順序は Enum の宣言順 で安定 → 表・グラフの並びがブレない
内部は 配列 実装 →
より高速・省メモリ
HashMap
ダッシュボード・帳票・メトリクス集計で刺さる
22
ステートマシンを Enum で表現
public enum DocumentState {
DRAFT
{ public Set<DocumentState> nextStates() { return Set.of(REVIEWING, CANCELLED); } },
REVIEWING { public Set<DocumentState> nextStates() { return Set.of(APPROVED, REJECTED, DRAFT); } },
APPROVED
{ public Set<DocumentState> nextStates() { return Set.of(PUBLISHED, CANCELLED); } },
PUBLISHED { public Set<DocumentState> nextStates() { return Set.of(ARCHIVED); } },
ARCHIVED
{ public Set<DocumentState> nextStates() { return Set.of(); } };
// 終端
public abstract Set<DocumentState> nextStates();
public DocumentState transitionTo(DocumentState next) {
if (!nextStates().contains(next))
throw new IllegalStateException(this + " → " + next + " は許可されていない");
return next;
}
}
状態と遷移ルールが Enum 1つに閉じる — 仕様書のようなコード
23
Enum で Singleton(Effective Java Item 3) public enum AppConfig { INSTANCE; // ← これだけで Singleton 完成 private int requestCount = 0; public void handleRequest(String msg) { requestCount++; /* ... */ } public int getRequestCount() { return requestCount; } } // 使う側 AppConfig.INSTANCE.handleRequest("hello"); クラスロード時に1回だけ初期化 → スレッドセーフ シリアライズ しても同一インスタンスが保たれる リフレクション攻撃 にも強い Effective Java 推奨。 private static final + ロックを書く必要なし 24
Enum の進化 switch 式の網羅性 25
switch 式の網羅性チェック(Java 14+)
String label = switch (status) {
case PENDING
-> "処理待ち";
case PROCESSING -> "処理中";
case COMPLETED
-> "完了";
case FAILED
-> "失敗";
// 全ケース網羅 → default 不要!
};
新しい定数を追加するとコンパイルエラーになる
error: the switch expression does not cover all possible input values
対処漏れをゼロに。
26
Enum vs Sealed Class それぞれの使いどき 27
Enum vs Sealed Class Enum Sealed Class / Interface インスタンス数 コンパイル時確定(固定) 実行時に複数作れる データの形 全定数で同じ型のフィールド サブクラスごとに異なる構造 switch 網羅性 ✓(Java 14+) ✓(Java 21+) シリアライズ 自動的に安全 要実装 使いどき 値の種類・状態が固定 形が異なるデータ // Enum が向く:「状態の種類」 enum OrderStatus { PENDING, SHIPPED, DELIVERED, CANCELLED } // Sealed が向く:「形が違うデータ」 sealed interface Shape permits Circle, Rectangle, Triangle {} 28
Sealed × パターンマッチング(Java 21+)
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double r) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}
static double area(Shape shape) {
return switch (shape) {
case Circle c
-> Math.PI * c.r() * c.r();
case Rectangle r -> r.w() * r.h();
case Triangle t
-> heron(t.a(), t.b(), t.c());
// sealed なので全ケース網羅 → default 不要!
};
}
型パターン で各サブクラスのフィールドにそのままアクセスできる
Enum の switch 網羅性が 「形の違うデータ」にまで拡張 されたイメージ
Enum を sealed interface の実装にも混ぜられる(特殊な単一定数など)
29
今日のまとめ! 1. Enum は「クラス」— フィールドと振る舞いを持たせろ コード値・ラベル・ロジックを Enum 1つに閉じ込める。Strategy も状態遷移もここで完結す る。 2. DB と Enum の間には Converter を挟め は使わない。Java の定数名と DB の値を切り離し、リネームの自由を確保する。 3. switch 式の網羅性で「対処漏れ」をコンパイル時に検出 を書かない勇気。定数追加 → 全 switch がコンパイルエラーで教えてくれる。 @Enumerated default Enum を「なんとなく」使う段階を卒業して、Java の型システムを最大限に活かしましょう! 30
ありがとうございました 名前: こうの 所属: 虎の穴ラボ テックリード Twitter: @hk_it7 31
サンプルコード & リンク サンプルコード Twitter 実況補助ツール https://github.com/h-kono-it/jjug_ccc_2026_spring-enum https://h-kono-it.github.io/conference-tweet-helper/ 32
付録:用語集 当日は口頭で補足した用語のまとめ 33
用語集(言語仕様・機能) メソッドシグネチャ — メソッドの「名前・引数の型」の組み合わせ。戻り値の型はシグネチャ に含まれない(JLS の定義)。 のような宣言部 分のこと。 JLS(Java Language Specification) — Java の言語仕様を定めた公式文書。Enum の動 作もここで規定されている。 switch 式(switch expression) — Java 14 で導入。 構文で値を返せる。switch 文 (statement)は値を返さず、 が必要だった。 Sealed Class / Interface — Java 17 で導入。 で実装・継承できるクラスを明示的 に制限できる。Java 21 以降のパターンマッチングと組み合わせると網羅性チェックが効く。 網羅性チェック(Exhaustiveness Check) — switch 式で全ケースが処理されているかをコ ンパイラが検証する仕組み。Enum・Sealed Class で有効になる。 List<Order> findByStatus(OrderStatus status) -> break permits 34
用語集(設計パターン・ORM) シングルトン(Singleton) — JVM 内にインスタンスが1つだけ存在することを保証するパタ ーン。Enum の各定数はすべてシングルトン。 Strategy パターン — 処理のアルゴリズムを切り替え可能にするデザインパターン。Enum の 抽象メソッドや Lambda フィールドで実現できる。 ポリモーフィズム(Polymorphism) — 同じ型・インターフェースで異なる振る舞いを実現す る OOP の概念。Enum の抽象メソッドや interface 実装で活用できる。 AttributeConverter — JPA(Spring Data JPA / Hibernate)で型変換を定義するインター フェース。Enum DB カラムの変換に使う。 TypeHandler — MyBatis で型変換を定義する仕組み。JPA の AttributeConverter に相当す る役割。 35