Java : レコードクラスの基本 (Record Class)

レコードクラスを使うと、単純なデータ構造のみを持つクラスをシンプルに記述できるようになります。
Java 16 の言語仕様として新しく追加されました。

本記事では、レコードクラスの基本的な使い方を解説していきます。


標準クラスとレコードクラスの比較

標準クラスの例

まずは、単純なデータ構造のみを持つクラスを考えてみましょう。

文字列を1つ、数値を1つ持つクラスを作りたいとします。
メソッドは、値を取得するメソッド(getter)のみを持ちます。

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }

    public String getTitle() {
        return title;
    }

    public int getTotalPage() {
        return totalPage;
    }
}

例としてBookクラスを作成しました。
タイトルと総ページ数が取得できます。

public class Book {
    private final String title;
    private final int totalPage;
...

変更は許可しないので、フィールドはすべて private final とします。

final var book = new Book("タイトル", 100);

System.out.println(book.getTitle()); // タイトル
System.out.println(book.getTotalPage()); // 100

Bookを作成して、タイトルとページを取得する例になります。
問題なく使えていますね。

レコードクラスの例

先ほどのBookクラスは、レコードクラスを使うとシンプルに記述することができます。

public record Book(String title, int totalPage) {
}

非常にシンプルになりました。

classキーワードの代わりに、recordキーワードを使います。
また、コンストラクタやフィールド、値を取得するメソッド(getter)の記述が不要になります。

final var book = new Book("タイトル", 100);

System.out.println(book.title()); // タイトル
System.out.println(book.totalPage()); // 100

実際にレコードクラスを使う例です。
newによる生成は同じです。

値を取得するメソッド名は、変数名そのままとなります。(getはつきません)


レコードクラスの特徴

フィールドの自動生成

public record Book(String title, int totalPage) {
}

レコードクラスは、private final のフィールドが自動で生成されます。
標準クラスで同じことを記述すると、下記のようになります。

public class Book {
    private final String title;
    private final int totalPage;
...

また、レコードクラスに独自のメソッドを追加して、自動で生成されたフィールドにもアクセスできます。

public record Book(String title, int totalPage) {

    public void printAll() {
        System.out.println("title : " + title);
        System.out.println("totalPage : " + totalPage);
    }
}
final var book = new Book("タイトル", 100);

//title : タイトル
//totalPage : 100
book.printAll();

コンストラクタの自動生成

public record Book(String title, int totalPage) {
}

レコードクラスは、自動生成されたフィールドに値を設定するコンストラクタが自動で生成されます。
標準クラスで同じことを記述すると、下記のようになります。

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }
...

また、オブジェクト生成時のパラメータの検証をしたいときは、コンパクト・コンストラクタが使えます。

public record Book(String title, int totalPage) {
    public Book {
        if (title == null || title.isEmpty()) {
            throw new IllegalArgumentException("titleが不正です : " + title);
        }
        if (totalPage < 0) {
            throw new IllegalArgumentException("totalPageが不正です : " + totalPage);
        }
    }
}
// IllegalArgumentException: titleが不正です : null
new Book(null, 100);

// IllegalArgumentException: totalPageが不正です : -1
new Book("タイトル", -1);

標準クラスで記述すると、下記のようになります。

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        if (title == null || title.isEmpty()) {
            throw new IllegalArgumentException("titleが不正です : " + title);
        }
        if (totalPage < 0) {
            throw new IllegalArgumentException("totalPageが不正です : " + totalPage);
        }

        this.title = title;
        this.totalPage = totalPage;
    }
...

値を取得するメソッド(getter)の自動生成

public record Book(String title, int totalPage) {
}
final var book = new Book("タイトル", 100);

System.out.println(book.title()); // タイトル
System.out.println(book.totalPage()); // 100

レコードクラスは、フィールドを取得するメソッドが自動で生成されます。
メソッド名は、パラメータ名そのままになります。(上の例ではtitletotalPage)

標準クラスで同じことを記述をすると、以下のようになります。

public class Book {
...
    public String title() {
        return title;
    }

    public int totalPage() {
        return totalPage;
    }
...

equals、hashCodeの自動実装

public record Book(String title, int totalPage) {
}
final var book1 = new Book("タイトル", 100);
final var book2 = new Book("タイトル", 100);

System.out.println(book1.equals(book2)); // true

System.out.println(book1.hashCode()); // -976936516
System.out.println(book2.hashCode()); // -976936516

レコードクラスは、Objectクラスのメソッドであるequals、hashCodeを自動でオーバーライドして実装します。

book1とbook2は同じ内容なので、equalsはtrueを返します。
hashCodeも同じ値を返します。
期待どおりですね。

標準クラスでは、equalsをオーバーライドしないとfalseを返します。
hashCodeは同じ値になることは保証されません。

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }
}
final var book1 = new Book("タイトル", 100);
final var book2 = new Book("タイトル", 100);

System.out.println(book1.equals(book2)); // false

System.out.println(book1.hashCode()); // 304216540
System.out.println(book2.hashCode()); // 1596462401

toStringの自動実装

public record Book(String title, int totalPage) {
}
final var book = new Book("タイトル", 100);

final var str = book.toString();
System.out.println(str); // Book[title=タイトル, totalPage=100]

レコードクラスは、ObjectクラスのメソッドであるtoStringを自動でオーバーライドして実装します。
実装されたtoStringでは、クラス名と各フィールドの内容を文字列として取得できます。

デバッグでオブジェクトの内容を出力させたいときに便利ですね。

標準クラスでは、toStringをオーバーライドしないと単純なインスタンス情報のみが取得されます。

public class Book {
    private final String title;
    private final int totalPage;

    public Book(String title, int totalPage) {
        this.title = title;
        this.totalPage = totalPage;
    }
}
final var book = new Book("タイトル", 100);

final var str = book.toString();
System.out.println(str); // Book@1221f9dc

公式ドキュメントのリンク

5 レコード・クラス
特殊な種類のクラスであるレコード・クラスは、通常のクラスよりも少ない手間でプレーン・データ集計をモデル化するために役立ちます。

Java の言語更新としてレコードクラスが紹介されています。
本記事では紹介しきれていないこともあるので、一読することをおすすめします。


8.10. Record Classes
A record declaration specifies a new record class, a restricted kind of class that defines a simple aggregate of values.

さらに詳しく知りたいかたは、言語仕様もご確認ください。(こちらは英語になります)

まとめ

個人的には、単純なデータ構造のクラスはよく作るので、とてもうれしい機能追加です。
equals、hashCode、toString を自動で実装してくれるのも良いですね。

いろいろと自動で生成してくれるので、記述量が減るのはもちろん、単純なコーディングミスが減ることも期待できます。


関連記事

ページの先頭へ