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
レコードクラスは、フィールドを取得するメソッドが自動で生成されます。
メソッド名は、パラメータ名そのままになります。(上の例では title と totalPage)
標準クラスで同じことを記述をすると、以下のようになります。
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 も同じ値を返します。期待どおりですね。
これは Map のキーとして使うときに便利です。
標準クラスでは、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
公式ドキュメントのリンク
Java の言語更新としてレコードクラスが紹介されています。
本記事では紹介しきれていないこともあるので、一読することをおすすめします。
さらに詳しく知りたいかたは、言語仕様もご確認ください。(こちらは英語になります)
まとめ
個人的には、単純なデータ構造のクラスはよく作るので、とてもうれしい機能追加です。
equals、hashCode、toString を自動で実装してくれるのも良いですね。
いろいろと自動で生成してくれるので、記述量が減るのはもちろん、単純なコーディングミスが減ることも期待できます。
関連記事
- Object.hashCode (ハッシュ・コード) とは
- if文の基本
- while文の基本
- for文の基本
- プリミティブ型 (基本データ型)
- リテラルの表記方法いろいろ
- クラスの必要最低限を学ぶ
- インタフェースの default メソッドとは
- インタフェースの static メソッドの使いどころ
- var (型推論) のガイドライン
- アクセス修飾子の基本
- 配列 (Array) の使い方
- 拡張for文 (for-eachループ文)
- switch文ではなくswitch式を使おう
- try-with-resources文でリソースを自動的に解放
- テキストブロックの基本
- 列挙型 (enum) の基本
- ラムダ式の基本
- メソッド参照の基本
- シールクラスの基本(Sealed Class)
- 無名変数の使い方
- API 使用例