Java 14 Records クラス

Java 14では、レコードと呼ばれるクラスの新しい作成方法が導入されました。このチュートリアルでは、以下の内容を学びます:

  • なぜJava Recordsが必要なのか
  • Recordsの作成と使用方法
  • Recordsクラスのオーバーライドと拡張

おすすめの読み物: Java 14の新機能

なぜJava Recordsが必要なのか

Javaの冗長さは、よく指摘されてきました。シンプルなPOJOクラスを作成する場合、以下のようなボイラープレートコードが必要です。

  • プライベートフィールド
  • ゲッターとセッターメソッド
  • コンストラクタ
  • hashCode()、equals()、toString()メソッド

この冗長さは、KotlinProject Lombokへの高い関心の一因です。

実際、これらの汎用メソッドを毎回記述することへのフラストレーションから、EclipseやIntelliJ IDEAなどのJava IDEでは、これらのメソッドを生成するためのショートカットが提供されています。

以下は、Eclipse IDEにおけるクラスのセレモニアルメソッド生成オプションのスクリーンショットです。

Eclipse Shortcuts to Generate Ceremonial Methods

Java Recordsは、POJOクラスの作成をコンパクトにすることで、この冗長さを取り除くことを目的としています。

Javaレコードの作成方法

Javaレコードは、JEP 359の下で開発されたプレビュー機能です。したがって、Javaプロジェクトでレコードを作成するには2つのことが必要です。

  1. JDK 14がインストールされていること。もしIDEを使用している場合は、Java 14のサポートが必要です。EclipseとIntelliJは既にJava 14のサポートを提供しているので、ここでは問題ありません。
  2. プレビュー機能を有効にする:デフォルトでは、プレビュー機能は無効になっています。Eclipseでは、プロジェクトのJavaコンパイラ設定から有効にすることができます。
Java 14 Enable Preview Feature In Eclipse

コマンドラインでJava 14のプレビュー機能を有効にするには、--enable-preview -source 14オプションを使用します。

例えば、Employeeモデルクラスを作成したいとします。以下のコードのようになります。

package com.journaldev.java14;

import java.util.Map;

public class Employee {

	private int id;
	private String name;
	private long salary;
	private Map<String, String> addresses;

	public Employee(int id, String name, long salary, Map<String, String> addresses) {
		super();
		this.id = id;
		this.name = name;
		this.salary = salary;
		this.addresses = addresses;
	}

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public long getSalary() {
		return salary;
	}

	public Map<String, String> getAddresses() {
		return addresses;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((addresses == null) ? 0 : addresses.hashCode());
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + (int) (salary ^ (salary >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (addresses == null) {
			if (other.addresses != null)
				return false;
		} else if (!addresses.equals(other.addresses))
			return false;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (salary != other.salary)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + ", addresses=" + addresses + "]";
	}

}

うわー、これは70行以上の自動生成コードです。さて、同じ機能を提供するEmployee Recordクラスを作成する方法を見てみましょう。

package com.journaldev.java14;

import java.util.Map;

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}

わあ、これ以上短くなることはありません。Recordクラスがすでに気に入っています。

さて、Recordがコンパイルされる際に裏で何が起こっているのかを理解するために、javapコマンドを使用しましょう。

# javac --enable-preview -source 14 EmpRecord.java
Note: EmpRecord.java uses preview language features.
Note: Recompile with -Xlint:preview for details.

# javap EmpRecord      
Compiled from "EmpRecord.java"
public final class EmpRecord extends java.lang.Record {
  public EmpRecord(int, java.lang.String, long, java.util.Map<java.lang.String, java.lang.String>);
  public java.lang.String toString();
  public final int hashCode();
  public final boolean equals(java.lang.Object);
  public int id();
  public java.lang.String name();
  public long salary();
  public java.util.Map<java.lang.String, java.lang.String> addresses();
}
# 
Java Record Class Details

# javac enablepreview source 14 EmpRecord.java

# javap EmpRecord

もし内部の詳細を知りたい場合は、javapコマンドを-vオプションとともに実行してください。

  1. A Record class is final, so we can’t extend it.
  2. # javap -v EmpRecord
  3. Recordクラスに関する重要なポイント
  4. レコードクラスは、java.lang.Recordクラスを暗黙的に拡張します。
  5. A single constructor is created with all the fields specified in the record definition.
  6. レコード宣言で指定されたフィールドはすべてfinalです。
  7. レコードのフィールドは「浅い」イミュータブルであり、その型に依存します。たとえば、アドレスフィールドをアクセスして更新することができます。

レコードクラスはフィールドのためのアクセサメソッドを自動的に提供します。メソッド名はフィールド名と同じですが、一般的なゲッターメソッドとは異なります。

レコードクラスはhashCode()、equals()、toString()の実装も提供します。

package com.journaldev.java14;

public class RecordTest {

	public static void main(String[] args) {
		
		EmpRecord empRecord1 = new EmpRecord(10, "Pankaj", 10000, null);
		EmpRecord empRecord2 = new EmpRecord(10, "Pankaj", 10000, null);

		Javaプログラムでレコードを使用する方法
		System.out.println(empRecord1);
		
		EmpRecordクラスの使用例を見てみましょう。
		System.out.println("Name: "+empRecord1.name()); 
		System.out.println("ID: "+empRecord1.id());
		
		// toString()
		System.out.println(empRecord1.equals(empRecord2));
		
		// フィールドへのアクセス
		System.out.println(empRecord1 == empRecord2);		
	}
}

// equals()

EmpRecord[id=10, name=Pankaj, salary=10000, addresses=null]
Name: Pankaj
ID: 10
true
false

// hashCode()

出力:

Recordオブジェクトは、モデルクラスやデータオブジェクトなどと同じように機能します。

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
	
	public EmpRecord {
		if (id < 0)
			throw new IllegalArgumentException("employee id can't be negative");

		if (salary < 0)
			throw new IllegalArgumentException("employee salary can't be negative");
	}

}

レコードコンストラクタの拡張

EmpRecord empRecord1 = new EmpRecord(-10, "Pankaj", 10000, null);

時によっては、コンストラクタにバリデーションやログを追加したい場合があります。たとえば、従業員IDや給与は負であってはなりません。デフォルトのコンストラクタにはこのバリデーションがありません。レコードクラスにコンパクトなコンストラクタを作成することができます。このコンストラクタのコードは自動生成されたコンストラクタの先頭に配置されます。

Exception in thread "main" java.lang.IllegalArgumentException: employee id can't be negative
	at com.journaldev.java14.EmpRecord.<init>(EmpRecord.java:9)

次のコードのようにEmpRecordを作成すると、実行時例外が発生します:

public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {

	public int getAddressCount() {
		if (this.addresses != null)
			return this.addresses().size();
		else
			return 0;
	}
}

レコードクラスにメソッドを持たせることはできますか?

はい、レコード内にメソッドを作成することができます。

ただし、レコードはデータキャリアとしての役割がありますので、ユーティリティメソッドをレコードクラス内に持たせることは避けるべきです。例えば、上記のメソッドはユーティリティクラスに作成することができます。

もし、Recordクラスにメソッドが必要だと考える場合、本当にRecordクラスが必要かよく考えてください。

Source:
https://www.digitalocean.com/community/tutorials/java-records-class