CodeGym /コース /JAVA 25 SELF /Record: 構文と利点

Record: 構文と利点

JAVA 25 SELF
レベル 22, レッスン 0
使用可能

1. DTO クラスの問題: なぜ record クラスが必要か?

正直に言いましょう。こんなクラスを何回書いたことがありますか?


public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public String toString() {
        return "Point{" + "x=" + x + ", y=" + y + '}';
    }
}

難しくはなさそうですが、行数を数えてみてください! そして同様のクラスが 10 個あり、それぞれに 5–6 個のフィールドがあると想像してみましょう。IDE でさえこのボイラープレート生成に疲れます。新しいフィールドを追加しようものなら、コンストラクタ、ゲッター、equals、hashCode、toString をすべて直す羽目に……。退屈で面倒、しかもバグの温床です。

この手のクラスは DTO(Data Transfer Object)や Value Object と呼ばれます。 単にデータを保持するだけ——それだけです。しかし、ボイラープレートのせいで保守が大変になります。

「大した問題じゃない」と思うなら、50 個の同種クラスを一斉に変更する時まで待ってみてください。そのとき、record クラスのありがたみをきっと痛感します!

2. record 入門: 構文と Java 16+ のマジック

Java 16 で状況は一変しました。新しい種類のクラス record が登場したのです。データの集合をただ保持したいケースのために設計されています。構文は他言語のタプルに近い感覚です。

record の宣言方法


public record Point(int x, int y) { }

これでおしまい! 今、2 つのフィールド、コンストラクタ、アクセサ、equals、hashCode、toString を持つ不変クラスができました。余計な記述は不要です。

Java は「内部で」何をしているのか?

  • フィールド x と y は private かつ final になります。
  • アクセサ は自動生成されます: int x() と int y()。
  • コンストラクタ: public Point(int x, int y)。
  • equals/hashCode: すべてのフィールドを値で比較します。
  • toString: "Point[x=1, y=2]" のような文字列を返します。

言ってみれば、record は「強化版 DTO」です。コードは少なく、保証は多く、バグは減ります。

不変(immutability)

record クラスのすべてのフィールドは自動的に final になります。オブジェクト生成後にそれを変更することはできず、そのことはコンパイラが保証します。

セッターを追加したり、フィールドを final でなくしようとすれば、コンパイラが止めてくれます。ここまで面倒を見てくれるのは珍しいことです!

3. record クラスの使用例

従来のクラス(コードが多い):


public class Client {
    private final String name;
    private final int id;

    public Client(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() { return name; }
    public int getId() { return id; }

    // equals, hashCode, toString ...
}

Record クラス(1 行!):


public record Client(String name, int id) { }

使用例:


public class Main {
    public static void main(String[] args) {
        Client client = new Client("イワン", 123);
        System.out.println(client.name()); // イワン
        System.out.println(client.id());   // 123
        System.out.println(client);        // Client[name=イワン, id=123]
    }
}

注意:
- フィールドへのアクセサメソッドはフィールド名と同じ: name()、id()。
- setName() や setId() はありません——生成後にオブジェクトを変更できないためです。

4. record クラスの利点: コードもバグも減らす

コードが少ない = 幸せが増える

40 行書く必要があるなら、1 行で済ませましょう。record クラスは時間と労力を節約します。特に DTO や value オブジェクトが多い大規模プロジェクトで威力を発揮します。

「契約として」の不変性

  • record クラスは常に final で、不変です。
  • オブジェクトを改ざん・誤変更しにくくなります。
  • 意図しない箇所で状態が変わることによる「奇妙な」バグがなくなります。
  • (すべてのフィールドも不変であれば)マルチスレッドでも安全に使えます。

equals/hashCode/toString の自動生成

比較、ハッシュ計算、見やすい出力のメソッドを手書きする必要はありません。すべて正しく自動生成されます。


Client c1 = new Client("アンナ", 42);
Client c2 = new Client("アンナ", 42);

System.out.println(c1.equals(c2));                 // true
System.out.println(c1.hashCode() == c2.hashCode()); // true
System.out.println(c1);                             // Client[name=アンナ, id=42]

コレクションやキーに最適

record オブジェクトは HashMap のキー、HashSet の要素などとして利用できます。equals と hashCode が全フィールドを考慮するため、正しく動作します。


import java.util.HashMap;
import java.util.Map;

Map<Client, String> clients = new HashMap<>();
clients.put(new Client("アンナ", 42), "VIP");

System.out.println(clients.get(new Client("アンナ", 42))); // VIP

データの明示的な記述

record クラスの構文を見れば、どんなデータを保持し、オブジェクトが不変であるかがひと目でわかります。これは他の開発者(そして半年後の自分)にとってコードを理解しやすくします。

5. 表: 従来のクラスと record クラスの比較

従来のクラス Record クラス
構文 コードが多い 1 行で定義
不変性 明示的な実装が必要 コンパイラが保証
メソッドの自動生成 なし あり(equals, hashCode, toString)
フィールド追加 可能 record のコンポーネントのみ
継承 継承可能 常に final。継承不可
コレクションでの利用 メソッドを正しく実装する必要 標準でそのまま動作

6. 学習用アプリを拡張: record クラスの例

学習用の銀行アプリで、口座の取引(日時、金額、取引タイプ(例: 「入金」「出金」))を保持するとしましょう。

Java 16 以前:


public class Transaction {
    private final LocalDate date;
    private final double amount;
    private final String type;

    public Transaction(LocalDate date, double amount, String type) {
        this.date = date;
        this.amount = amount;
        this.type = type;
    }

    public LocalDate getDate() { return date; }
    public double getAmount() { return amount; }
    public String getType() { return type; }

    // equals, hashCode, toString ...
}

Java 16 と record の場合:


import java.time.LocalDate;

public record Transaction(LocalDate date, double amount, String type) { }

使用例:


Transaction t = new Transaction(LocalDate.now(), 100.0, "deposit");
System.out.println(t);         // Transaction[date=2024-06-01, amount=100.0, type=deposit]
System.out.println(t.amount()); // 100.0

7. 可視化: record が生成するもの

「展開した」record クラス(コンパイラが概ね生成するもの)は次のようになります。


public final class Point extends java.lang.Record {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }

    @Override
    public boolean equals(Object o) { /* フィールドによる比較 */ }

    @Override
    public int hashCode() { /* フィールドに基づく計算 */ }

    @Override
    public String toString() { /* 見やすい出力 */ }
}

まとめ: record を使うべきとき

  • 不変なデータの集合を持つオブジェクトが必要なとき。
  • 手書きなしで「正しい」equals/hashCode/toString が必要なとき。
  • DTO、Value Object、ペアやタプル、色、点、範囲、コレクションのキーなどを作るとき。

8. record クラスでありがちな誤り

エラー No.1: セッターを追加したり、生成後にフィールドを変更しようとする。
record クラスは自分のフィールドを変更できません。setX(int x) のようなメソッドを追加しようとすると、コンパイラがすぐに拒否します。フィールドを直接変更しようとしても同様です。

エラー No.2: 非 static フィールドを追加しようとする。
record クラスで宣言できるのはコンポーネント(record 名の後の丸括弧内に書くフィールド)と static フィールドだけです。通常の非 static フィールドは追加できず、コンパイラが許しません。

エラー No.3: mutable なロジックに record を使う。
record クラスは可変状態を持つオブジェクト向けではありません。生成後に状態を変える必要があるなら、従来のクラスを使いましょう。

エラー No.4: record は常に final であることを忘れる。
record クラスは継承できず、かつスーパークラスにもできません。この制約に反するとコンパイルエラーになります。重要なポイントは、record を「拡張」しようとしないことです。record は完結した不変型として設計されています。

エラー No.5: 自動生成メソッドを軽視する。
equals、hashCode、toString をオーバーライドする場合は要注意です——その契約を破らないようにしないと、コレクションや比較が正しく動かなくなります。

1
タスク
JAVA 25 SELF, レベル 22, レッスン 0
ロック未解除
書籍のデジタルカードを作成する 📚
書籍のデジタルカードを作成する 📚
1
タスク
JAVA 25 SELF, レベル 22, レッスン 0
ロック未解除
学生データの閲覧 🎓
学生データの閲覧 🎓
1
タスク
JAVA 25 SELF, レベル 22, レッスン 0
ロック未解除
地図上の都市の同一性の確認 🗺️
地図上の都市の同一性の確認 🗺️
1
タスク
JAVA 25 SELF, レベル 22, レッスン 0
ロック未解除
アプリケーションのユーザーアクセス管理 🔑
アプリケーションのユーザーアクセス管理 🔑
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION