CodeGym /コース /JAVA 25 SELF /record と class の違い、record の制約

record と class の違い、record の制約

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

1. record と class の比較: 主な違いは何か?

Java ではデータ型を定義する主な方法が 2 つあります。通常のクラス(class)と record クラス(record)です。一見どちらもデータの保持と処理ができますが、少し掘り下げると違いは想像以上に多いのです。

違いの表: class vs record

特徴 通常のクラス (class) record クラス (record)
可変性 任意: フィールドを final にしても、しなくてもよい 不変: すべてのフィールドが final
継承 継承可能(extends)。既定では final ではない 常に final。スーパークラスにはなれない
フィールド 任意: static/非static、final/非final、任意の型 record のコンポーネントのみ(private final)、加えて static フィールド
ゲッター/セッター 自分で実装(または Lombok で生成) ゲッターは自動生成(メソッド名はフィールド名と同じ)、セッターはない
equals/hashCode/toString 通常は手書き/生成(equals、hashCode、toString) 全コンポーネントに基づき自動生成
コンストラクタ 任意、いくつでも定義可能 主要コンストラクタが 1 つ(全コンポーネントを受け取る)。コンパクトコンストラクタを追加可能
インターフェース 実装可能 実装可能
追加メソッド 任意に追加可能 追加可能だがメソッドのみ(フィールドは不可)
コレクションでの利用 可能。ただし equals/hashCode を正しく実装する必要あり キー/値に理想的。必要な実装は既に提供済み

分かりやすい例

通常のクラス:


public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

record クラス:


public record Person(String name, int age) { }

以上です! 1 行のコードで同等(それ以上)のものが得られます。しかも、重要な実装をうっかり書き忘れるリスクもありません。

2. record クラスの制約

record クラスは単なる「短い構文」ではなく、独立した概念であり、厳密な規則があります。詳しく見ていきましょう。

record は常に final

record クラスは定義上いつでも final です。つまり、record からサブクラスを作成することはできません:


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

// public class ColoredPoint extends Point { } // コンパイルエラー!

振る舞いを拡張したい場合は、通常のクラスやコンポジション(record をクラスに内包)を使いましょう。

record はスーパークラスになれない

record クラスは他のクラスの親にはなれず、常に final です。これは当然です。もし可能だと、誰かが可変フィールドを追加できてしまい、「不変データ」というコンセプトが崩れてしまいます。

final フィールド(コンポーネント)のみ

record のコンポーネントはヘッダで宣言され、既定で private final です。record の本体にインスタンス(非static)フィールドを追加することはできません:


public record User(String login, String email) {
    // int counter; // エラー! 非static フィールドは追加できない
    static int totalUsers = 0; // これは許可される(static フィールド)
}

セッターはない

record クラスにはコンポーネント用のセッターを持てません。たとえば setX(int x) のようなメソッドを追加しても意味がありません。オブジェクト生成後に値を変更することはできないからです。


public record Point(int x, int y) {
    // public void setX(int x) { this.x = x; } // エラー: final フィールドは変更できない
}

引数なしコンストラクタはない

record クラスには常に、すべてのコンポーネントを受け取る主コンストラクタしかありません。すべての値を指定せずに record を作ることはできません:


Point p = new Point(1, 2);  // OK
// Point p = new Point();   // エラー: 引数なしのコンストラクタはない

インスタンス初期化子は使えない

record クラスでは、メソッド外のインスタンス初期化子(ブロック)は使用できません:


public record User(String login) {
    // { /* ... */ } // エラー: インスタンス初期化子は禁止
}

継承に関する制約

record クラスは他のクラスを明示的に継承できません(java.lang.Record は、すべての record の基底クラスとして暗黙に継承されます)。一方で、インターフェースの実装は可能です。


public interface Printable {
    void print();
}

public record Book(String title) implements Printable {
    @Override
    public void print() {
        System.out.println("本を印刷: " + title);
    }
}

複雑なビジネスロジックには不向き

record はデータ志向であり、振る舞いを重視するものではありません。オブジェクトに複雑なロジック、可変状態、「ライフサイクル」や多数の依存があるなら、record は適しません。通常のクラスを使いましょう。

3. record クラスを使うべき場面

  • DTO (Data Transfer Object): アプリケーションの層間、サービス間、マイクロサービスや REST コントローラ間で不変データを受け渡す用途(例: JSON レスポンス)。
  • Value Object: 値のみで同一性が決まるオブジェクト。
  • コレクションのキーや値: equals と hashCode の正しい実装が重要な場合(例: HashMap や Set での利用)。
  • 計算結果: メソッドから複数の値をまとめて返したい場合(例: record Pair<T, U>(T first, U second))。

例: REST コントローラ用の DTO


public record UserDto(String login, String email) { }

これで、誰かにフィールドを変更される心配なく、この型のオブジェクトをコントローラから安心して返せます。

例: HashMap のキー


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

Map<Point, String> pointNames = new HashMap<>();
pointNames.put(new Point(1, 2), "A");
pointNames.put(new Point(3, 4), "B");

// すべて正しく動作します: equals と hashCode は既に実装済み!

4. record クラスを使うべきでない場面

  • 可変な状態: オブジェクト生成後に少なくとも 1 つのフィールドを変更する必要がある場合。
  • 複雑なロジック: 多くのメソッドや、可変状態を持つ入れ子のオブジェクトがある場合。
  • 継承: クラス階層や抽象基底クラス、メソッドのオーバーライドが必要な場合。
  • ビジネスエンティティ: 例として、データベース上に存在し固有 ID を持つオブジェクトなど。

例: 通常のクラスが必要な場合


public class Account {
    private String id;
    private int balance;

    public Account(String id, int balance) {
        this.id = id;
        this.balance = balance;
    }

    public void deposit(int amount) { balance += amount; }
    public void withdraw(int amount) { balance -= amount; }
    // getters, setters, equals, hashCode, toString...
}

このようにオブジェクトの状態が変化するのは明らかなので、record には不向きです。

5. 実践例: record と class の選び方

例 1: record が最適


public record Rectangle(int width, int height) {
    public int area() {
        return width * height;
    }
}
  • この長方形は幅と高さだけで定義されます。
  • 生成後にこれらの値を変更する必要はありません。
  • 有用なメソッド area() を追加できます。
  • その他は Java が面倒を見てくれます。

例 2: class が最適


public class MutableRectangle {
    private int width;
    private int height;

    public MutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }

    public int area() { return width * height; }
}

生成後に長方形のサイズを変更したいですか? 通常のクラスを使いましょう。

6. record クラスでよくあるミス

エラー №1: インスタンス(非static)フィールドを追加しようとする。
record クラスでは、コンポーネント一覧の外側でインスタンスフィールドを宣言できません。そうしようとするとコンパイルエラーになります。例:


public record City(String name) {
    // int population; // エラー!
}

エラー №2: セッターを追加したくなる。
record はコンポーネントのセッターをサポートしません。オブジェクト生成後にフィールド値を変更しようとすると、コンパイルエラーになります。

エラー №3: record を継承する/record から継承させる。
record は常に final です。record を継承したり、record が他のクラス(暗黙の java.lang.Record を除く)を継承することはできません。

エラー №4: 可変オブジェクトに record を使う。
生成後にオブジェクトの状態を変更する予定があるなら、record は向きません! 通常のクラスを使いましょう。

エラー №5: コンストラクタの制約を忘れる。
record クラスには、すべてのコンポーネントを受け取るコンストラクタが必須です。引数なしコンストラクタはありません。

1
タスク
JAVA 25 SELF, レベル 22, レッスン 4
ロック未解除
住所を内側に持つ会社カード 🏢
住所を内側に持つ会社カード 🏢
1
タスク
JAVA 25 SELF, レベル 22, レッスン 4
ロック未解除
地図上の座標一致の確認 🗺️
地図上の座標一致の確認 🗺️
1
タスク
JAVA 25 SELF, レベル 22, レッスン 4
ロック未解除
ドキュメントの内容を印刷する 📄
ドキュメントの内容を印刷する 📄
1
タスク
JAVA 25 SELF, レベル 22, レッスン 4
ロック未解除
可変と不変のユーザープロファイルの比較 🧑‍🤝‍🧑
可変と不変のユーザープロファイルの比較 🧑‍🤝‍🧑
1
アンケート/クイズ
Recordクラス、レベル 22、レッスン 4
使用不可
Recordクラス
Recordクラス
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION