☕️
Javaのレコードについてメモ:コンストラクター
レコードについて軽く調べてメモっておくシリーズ。
今回はコンストラクターについてです。
環境
$ java --version
java 17 2021-09-14 LTS
Java(TM) SE Runtime Environment (build 17+35-LTS-2724)
Java HotSpot(TM) 64-Bit Server VM (build 17+35-LTS-2724, mixed mode, sharing)
コンストラクターの概要
言語仕様の8.10.4. Record Constructor Declarationsにコンストラクターのことが書かれています。
ざっくりと次のような感じみたいです。
- すべてのコンポーネントフィールドを初期化するためのパラメーターリストを受け取るコンストラクターをcanonical constructorと呼ぶっぽい
- canonical constructorは暗黙的に宣言されるが、明示的に宣言してもよい
- canonical constructorを明示的に宣言する場合、従来通りのコンストラクターとコンパクトコンストラクターの2通りがある
- デフォルトコンストラクターは暗黙的に宣言されない
- 非canonical constructorを宣言してもよいが、その先頭で明示的にcanonical constructorを呼び出す必要がある
コンパクトコンストラクターはレコードクラスの単純名のあとにブロックを置いた形式です。
public record Example(String text) {
public Example { // コンパクトコンストラクター
// ここで事前条件のチェックができる
Objects.requireNonNull(text);
}
}
コンパクトコンストラクターにはreturn
を含められないようです。
Static InitializerやInstance Initializerと同じですね。
ちなみに、レコードにはInstance Initializerが書けません。
シリアライズ
仕様に書かれていた次の一文が気になりました。
The serialization mechanism treats instances of a record class differently than ordinary serializable or externalizable objects. In particular, a record object is deserialized using the canonical constructor (§8.10.4).
通常、オブジェクトがデシリアライズされるときにコンストラクターは呼び出されないのですが、レコードはcanonical constructorが呼び出されるみたいです。
次のコードで確認してみました。
package example.record;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class RecordDeserializeDemo {
record Rec(String text) implements Serializable {
public Rec {
System.out.println("Recのcanonical constructorが呼び出されました。text = " + text);
}
}
// 比較のため通常のクラスも用意する
static final class Obj implements Serializable {
private final String text;
public Obj(String text) {
System.out.println("Objのコンストラクターが呼び出されました。text = " + text);
this.text = text;
}
public String text() {
return text;
}
}
public static void main(String[] args) throws Exception {
Rec rec1 = new Rec("demo");
System.out.println("rec1.text = " + rec1.text());
Obj obj1 = new Obj("demo");
System.out.println("obj1.text = " + obj1.text());
System.out.println("シリアライズします");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(rec1);
out.writeObject(obj1);
out.flush();
out.close();
System.out.println("デシリアライズします");
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Rec rec2 = (Rec) in.readObject();
System.out.println("rec2.text = " + rec2.text());
Obj obj2 = (Obj) in.readObject();
System.out.println("obj2.text = " + obj2.text());
}
}
出力は次の通りでした。
Recのcanonical constructorが呼び出されました。text = demo
rec1.text = demo
Objのコンストラクターが呼び出されました。text = demo
obj1.text = demo
シリアライズします
デシリアライズします
Recのcanonical constructorが呼び出されました。text = demo
rec2.text = demo
obj2.text = demo
確かにデシリアライズ時にcanonical constructorが呼び出されていますね。
以上です。
Discussion