Rec-03: Record vs Class vs Enum の使い分け
Java には「データを保持する」「ビジネスロジックを実行する」「定数を列挙する」という3つの異なる目的に対して、 それぞれ適した仕組みがあります。注文管理の例を使って、Record・Class・Enum の役割分担を理解しましょう。
3つの仕組みの使い分け基準
それぞれの仕組みには明確な「得意なこと」があります。 「どれを使えばいいか迷ったとき」は、下の表の「主な用途」を確認してください。
| 仕組み | 主な用途 | 状態 | 典型的な例 |
|---|---|---|---|
| record(Java 16+) | 値の保持・DTO・値オブジェクト | 不変(イミュータブル) | OrderDto、ApiResponse、Point |
| 通常クラス(class) | ビジネスロジック・サービス | 可変状態を持てる | OrderService、UserRepository |
| enum | 固定の定数セット | 種類が決まっている | OrderStatus、DayOfWeek、Season |
判断フロー
- 「取りうる値が決まっていて、追加されることがほとんどない」 → Enum(PENDING / PROCESSING / SHIPPED など)
- 「データを保持するだけで、作成後に値を変える必要がない」 → record(Java 16+)または
finalクラス - 「カウンターや状態を持ち、メソッドで変化させる必要がある」 → 通常クラス
サンプルコード
Java 8 では record が使えないため、値オブジェクト(DTO)は final フィールド・コンストラクタ・アクセサ・equals/hashCode/toString を手書きする必要があります。Java 16+ の record ではこれらがすべて自動生成されます。
よくあるミス・注意点
⚠️ すべてを record にする(可変状態が必要な場合は通常クラス)
record は不変(イミュータブル)なので、フィールドの値を後から変更できません。 「処理件数をカウントする」「途中でステータスを更新する」など可変状態が必要な場合は、 通常のクラスを使ってください。record と通常クラスを区別する基準は「作成後に状態が変わるか」です。
⚠️ Enum にビジネスロジックを詰め込みすぎる
Enum はラベル(getLabel())や単純な属性を持つことができますが、 「どのステータスに遷移できるか」のような複雑なビジネスロジックを Enum に追加すると肥大化します。 遷移ルールが複雑になった場合は、OrderService や専用のサービスクラスに切り出しましょう。 Enum は「定数の種類と簡単な属性」に留めるのがすっきりした設計です。
テストする観点
withStatus()を呼び出した後、元のOrderDtoのステータスが変わっていないこと(不変性の確認)withStatus()が返す新しいインスタンスが元と別オブジェクト(!=)で、ステータスが更新されていることOrderStatus.getLabel()が各ステータスに対して正しい日本語ラベルを返すこと(境界値: PENDING・CANCELLED の両端)OrderService.advance()が PENDING → PROCESSING → SHIPPED の順で遷移することOrderService.getProcessedCount()がadvance()を呼ぶたびに正確にカウントアップすること