Java 14のレコードクラス
Java 14では、Recordsと呼ばれるクラスの新しい作成方法が導入されました。このチュートリアルでは、以下のことを学びます:
- Why do we need Java Records
- How to create Records and use it
- Overriding and extending Records classes
おすすめの読み物:Java 14の特徴
なぜJavaのレコードが必要なのでしょうか? (Naze Java no rekōdo ga hitsuyō na nodeshouka?)
Javaに関する一般的な不満の一つは、冗長さです。シンプルなPOJOクラスを作成する場合、以下のボイラープレートコードが必要です。
- Private fields
- Getter and Setter Methods
- Constructors
- hashCode(), equals(), and toString() methods.
この冗長さは、KotlinとProject Lombokへの高い関心の一つの理由です。
実際に、これらの一般的なメソッドを毎回書くことに対する深い欲求不満は、EclipseやIntelliJ IDEAなどのJava IDEでそれらを作成するためのショートカットにつながりました。
以下は、クラスのセレモニアルなメソッドを生成するためのEclipse IDEオプションを示すスクリーンショットです。
Java Recordsは、POJOクラスを作成するためのコンパクトな構造を提供することで、この冗長さを取り除くことを目的としています。
「Java Records の作り方はどうすればいいですか?」
Java Recordsは、JEP 359の下で開発されたプレビュー機能です。したがって、JavaプロジェクトでRecordsを作成するためには2つの必要な要素があります。
-
- JDK 14がインストールされています。もしIDEを使用している場合は、そのIDEもJava 14をサポートしている必要があります。EclipseとIntelliJは既にJava 14をサポートしているため、問題ありません。
- プレビューフィーチャーの有効化: プレビューフィーチャーはデフォルトで無効になっています。EclipseではプロジェクトのJavaコンパイラ設定から有効にすることができます。
コマンドラインで、–enable-preview -source 14 オプションを使用して、Java 14のプレビュー機能を有効にすることができます。
従業員のモデルクラスを作成したいとしましょう。以下のコードのようになるでしょう。
package com.scdev.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行以上ですね。では、同じ機能を提供する従業員レコードクラスを作成する方法を見てみましょう。
package com.scdev.java14;
import java.util.Map;
public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}
わお、これ以上は短くならないね。もう、Recordクラスに夢中だよ。
今、javapコマンドを使用して、Recordがコンパイルされる際に裏側で何が起こっているかを調べてみましょう。
# 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();
}
#
もし内部の詳細をもっと知りたい場合は、javapコマンドを-vオプション付きで実行してください。
# javap -v EmpRecord
レコードクラスに関する重要なポイント
-
- レコードクラスはfinalなので、拡張することはできません。
-
- レコードクラスは、暗黙的にjava.lang.Recordクラスを拡張します。
-
- レコード宣言で指定されたすべてのフィールドはfinalです。
-
- レコードのフィールドは「浅い」不変であり、型に依存します。たとえば、アドレスフィールドはアクセスして更新することで変更できます。
-
- レコード定義で指定されたすべてのフィールドを持つ単一のコンストラクタが作成されます。
-
- レコードクラスは、フィールドに対して自動的にアクセサメソッドも提供します。メソッド名はフィールド名と同じであり、一般的なゲッターメソッドとは異なります。
- レコードクラスは、hashCode()、equals()、toString()の実装も提供します。
Javaプログラムでレコードを使用する
私たちのEmpRecordクラスの使用の簡単な例を見てみましょう。
package com.scdev.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);
// toString()
System.out.println(empRecord1);
// accessing fields
System.out.println("Name: "+empRecord1.name());
System.out.println("ID: "+empRecord1.id());
// equals()
System.out.println(empRecord1.equals(empRecord2));
// hashCode()
System.out.println(empRecord1 == empRecord2);
}
}
出力:
EmpRecord[id=10, name=Pankaj, salary=10000, addresses=null]
Name: Pankaj
ID: 10
true
false
レコードオブジェクトは、モデルクラスやデータオブジェクトなどと同じように機能します。
レコードのコンストラクタを拡張する。
時には、私たちはコンストラクタにいくつかの検証やログを追加したいと思うことがあります。例えば、従業員のIDや給与は負の値であってはなりません。デフォルトのコンストラクタでは、この検証は行われません。レコードクラスにコンパクトコンストラクタを作成することができます。このコンストラクタのコードは、自動生成されるコンストラクタの先頭に配置されます。
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を作成する場合
EmpRecord empRecord1 = new EmpRecord(-10, "Pankaj", 10000, null);
ランタイム例外が発生します。
Exception in thread "main" java.lang.IllegalArgumentException: employee id can't be negative
at com.scdev.java14.EmpRecord.<init>(EmpRecord.java:9)
レコードクラスにはメソッドを持つことはできますか? (Rekōdo kurasu ni wa mesoddo o motsu koto wa dekimasu ka?)
はい、私たちはレコードでメソッドを作成することができます。
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クラスが必要かよく考えてください。
結論
Javaのレコードは、コアプログラミング機能への歓迎すべき追加です。これは「名前付きタプル」と考えることができます。煩雑なコードを避け、コンパクトな構造のデータキャリアオブジェクトを作成するためのものです。