出典: abhinavpandey.dev このチュートリアルでは、Java でのレコードの使用の基本について説明します。レコードは、不変オブジェクトを利用しながら、Value オブジェクトの作成に関するボイラープレート コードを削除する方法として Java 14 で導入されました。
1. 基本的な考え方
エントリ自体に入る前に、エントリが解決する問題を見てみましょう。これを行うには、Java 14 より前に値オブジェクトがどのように作成されたかを思い出す必要があります。1.1. 値オブジェクト
値オブジェクトは Java アプリケーションに不可欠な部分です。アプリケーション層間で転送する必要があるデータを保存します。値オブジェクトには、フィールド、コンストラクター、およびそれらのフィールドにアクセスするためのメソッドが含まれています。以下は値オブジェクトの例です。public class Contact {
private final String name;
private final String email;
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
1.2. Value オブジェクト間の等価性
値オブジェクトは、それらが等しいかどうかを比較する方法も提供します。デフォルトでは、Java はメモリ アドレスを比較することによってオブジェクトの同等性を比較します。ただし、場合によっては、同じデータを含むオブジェクトが同等であるとみなされることがあります。これを実装するには、 equals メソッドと.hashCodeメソッドをオーバーライドします。これらをContactクラスに実装してみましょう。public class Contact {
// ...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Contact contact = (Contact) o;
return Object.equals(email, contact.email) &&
Objects.equals(name, contact.name);
}
@Override
public int hashCode() {
return Objects.hash(name, email);
}
}
1.3. Value オブジェクトの不変性
値オブジェクトは不変でなければなりません。これは、オブジェクトのフィールドを変更する方法を制限する必要があることを意味します。これは次の理由から推奨されます。- フィールド値を誤って変更するリスクを回避するため。
- 同等のオブジェクトが生涯を通じて同じ状態を維持できるようにするため。
- フィールドをprivateおよびFinalにしました。
- 各フィールドにはゲッターのみが提供されました(セッターはありません)。
1.4. 値オブジェクトの登録
多くの場合、オブジェクトに含まれる値を登録する必要があります。これは、 toStringメソッドを提供することによって行われます。オブジェクトが登録または印刷されるたびに、toStringメソッドが呼び出されます。ここでの最も簡単な方法は、各フィールドの値を出力することです。以下に例を示します。public class Contact {
// ...
@Override
public String toString() {
return "Contact[" +
"name='" + name + '\'' +
", email=" + email +
']';
}
}
2. レコードを使用してテンプレートを削減する
ほとんどの値オブジェクトは同じニーズと機能を備えているため、それらの作成プロセスを簡素化することは望ましいことです。録音がこれを達成するのにどのように役立つかを見てみましょう。2.1. Person クラスを Record に変換する
上記で定義したContactクラスと同じ機能を持つContactクラス エントリを作成しましょう。public record Contact(String name, String email) {}
Record キーワードは、Recordクラスの作成に使用されます。呼び出し元はレコードをクラスと同じ方法で処理できます。たとえば、新しいエントリ インスタンスを作成するには、 newキーワードを使用できます。
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");
2.2. デフォルトの動作
コードを 1 行に削減しました。何が含まれているかリストしてみましょう:-
名前と電子メールのフィールドは、デフォルトでは非公開で最終的なものになります。
-
このコードは、フィールドをパラメーターとして受け取る「標準コンストラクター」を定義します。
-
フィールドには、 getter のようなメソッド、name()およびemail()を通じてアクセスできます。フィールドにはセッターがないため、オブジェクト内のデータは不変になります。
-
Contactクラスの場合と同様に、フィールドを出力するためのtoStringメソッドを実装しました。
-
実装されたequalsメソッドと.hashCodeメソッド。Contactクラスと同様に、すべてのフィールドが含まれます。
2.3 正規コンストラクター
デフォルトのコンストラクターは、すべてのフィールドを入力パラメーターとして受け取り、それらをフィールドに設定します。たとえば、デフォルトの Canonical コンストラクターを以下に示します。public Contact(String name, String email) {
this.name = name;
this.email = email;
}
記録クラスで同じシグネチャを持つコンストラクターを定義すると、それが正規のコンストラクターの代わりに使用されます。
3. レコードの操作
エントリの動作はいくつかの方法で変更できます。いくつかのユースケースとその実現方法を見てみましょう。3.1. デフォルトの実装をオーバーライドする
デフォルトの実装はオーバーライドすることで変更できます。たとえば、toStringメソッドの動作を変更したい場合は、中かっこ{}の間でオーバーライドできます。public record Contact(String name, String email) {
@Override
public String toString() {
return "Contact[" +
"name is '" + name + '\'' +
", email is" + email +
']';
}
}
同様に、 equals メソッドとhashCodeメソッド をオーバーライドできます。
3.2. コンパクトな組み立てキット
場合によっては、コンストラクターに単にフィールドを初期化する以上のことを実行させたいことがあります。これを行うには、Compact Constructor のエントリに必要な操作を追加します。フィールドの初期化やパラメータ リストを定義する必要がないため、コンパクトと呼ばれます。public record Contact(String name, String email) {
public Contact {
if(!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
}
パラメータのリストはなく、チェックが実行される前に名前と電子メールの初期化がバックグラウンドで行われることに注意してください。
3.3. コンストラクターの追加
1 つのレコードに複数のコンストラクターを追加できます。いくつかの例と制限事項を見てみましょう。まず、新しい有効なコンストラクターを追加しましょう。public record Contact(String name, String email) {
public Contact(String email) {
this("John Doe", email);
}
// replaces the default constructor
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
}
最初のケースでは、thisキーワードを使用してデフォルトのコンストラクターにアクセスします。2 番目のコンストラクターは、同じパラメーター リストを持つため、デフォルト コンストラクターをオーバーライドします。この場合、エントリ自体はデフォルトのコンストラクターを作成しません。コンストラクターにはいくつかの制限があります。
1. デフォルトのコンストラクターは、常に他のコンストラクターから呼び出される必要があります。
たとえば、以下のコードはコンパイルできません。public record Contact(String name, String email) {
public Contact(String name) {
this.name = "John Doe";
this.email = null;
}
}
このルールにより、フィールドが常に初期化されることが保証されます。また、コンパクト コンストラクターで定義された操作が常に実行されることも保証されます。
2. コンパクト コンストラクターが定義されている場合、デフォルト コンストラクターをオーバーライドすることはできません。
コンパクト コンストラクターが定義されると、初期化とコンパクト コンストラクター ロジックを使用してデフォルト コンストラクターが自動的に作成されます。この場合、コンパイラーは、デフォルトのコンストラクターと同じ引数を持つコンストラクターを定義することを許可しません。たとえば、次のコードではコンパイルは行われません。public record Contact(String name, String email) {
public Contact {
if(!email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
}
public Contact(String name, String email) {
this.name = name;
this.email = email;
}
}
3.4. インターフェースの実装
他のクラスと同様に、レコードにインターフェイスを実装できます。public record Contact(String name, String email) implements Comparable<Contact> {
@Override
public int compareTo(Contact o) {
return name.compareTo(o.name);
}
}
重要な注意点。完全な不変性を確保するために、レコードを継承することはできません。エントリは最終的なものであり、拡張することはできません。また、他のクラスを拡張することもできません。
3.5. メソッドの追加
メソッドやインターフェイス実装をオーバーライドするコンストラクターに加えて、必要なメソッドを追加することもできます。例えば:public record Contact(String name, String email) {
String printName() {
return "My name is:" + this.name;
}
}
静的メソッドを追加することもできます。たとえば、電子メールをチェックできる正規表現を返す静的メソッドが必要な場合は、次のように定義できます。
public record Contact(String name, String email) {
static Pattern emailRegex() {
return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
}
}
3.6. フィールドの追加
インスタンス フィールドをレコードに追加することはできません。ただし、静的フィールドを追加することはできます。public record Contact(String name, String email) {
private static final Pattern EMAIL_REGEX_PATTERN = Pattern
.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
static Pattern emailRegex() {
return EMAIL_REGEX_PATTERN;
}
}
静的フィールドには暗黙の制限がないことに注意してください。必要に応じて、最終的なものではなく、一般に公開される場合があります。
GO TO FULL VERSION