Java 记录序列化和对规范构造函数的重复调用

Java record serialization and repeated calls to canonical constructor

this post about serializable records中指出

Deserialization creates a new record object by invoking a record class’s canonical constructor, passing values deserialized from the stream as arguments to the canonical constructor. This is secure because it means the record class can validate the values before assigning them to fields, just like when an ordinary Java program creates a record object via new. “Impossible” objects are impossible.

这与仅用于验证的构造函数存在争议。但是,当构造函数操纵参数时,这会导致相当奇怪的行为。考虑这个非常人为的简单示例:

以下记录在保存前操作 a

import java.io.Serializable;

public record TRecord (int a) implements Serializable {
    public TRecord {
        a = a-1;
    }
}

下面的程序只是第一次保存序列化记录并在随后的时间加载它:

import java.io.*;

public class TestRecords {

    public static void main(String args[]) {
        TRecord a1 = null;

        try {
            FileInputStream fileIn = new FileInputStream("tmp");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            a1 = (TRecord) in.readObject();
            in.close();
            fileIn.close();
        } catch (IOException | ClassNotFoundException i) {
            // ignore for now
        }
        if (a1 == null) {
            try {
                a1 = new TRecord(5);
                FileOutputStream fileOut = new FileOutputStream("tmp");
                ObjectOutputStream out = new ObjectOutputStream(fileOut);
                out.writeObject(a1);
                out.close();
                fileOut.close();
                System.out.printf("Serialized data is saved in /tmp/employee.ser");
            } catch (IOException i) {
                i.printStackTrace();
            }
        }

        System.out.println(a1);
    }
}

第一个 运行 的输出是 TRecord[a=4],随后 运行 的输出是 TRecord[a=3],所以我从反序列化中得到的状态与我输入的不同在那里。使用像下面这样的可比较的 class 每次都会得到相同的结果 TClass[a=4]

import java.io.Serializable;

public class TClass implements Serializable {
    private int a;

    public TClass(final int a) {
        this.a = a-1;
    }

    public int getA() {return a;}

    public String toString() {
        return "Class[" + a + "]";
    }
}

所以我的问题是:是否有任何记录规则 forbids/discourages 使用构造函数进行验证以外的任何事情(例如,我正在考虑在存储输入之前对密码进行哈希处理)?或者是否有另一种方法可以反序列化对象以恢复初始状态?

如果您查看 records 的文档,它说明如下:

For all record classes, the following invariant must hold: if a record R's components are c1, c2, ... cn, then if a record instance is copied as follows:

 R copy = new R(r.c1(), r.c2(), ..., r.cn());  

then it must be the case that r.equals(copy).

您的记录并非如此class但是:

jshell> TRecord r1 = new TRecord(42);
r1 ==> TRecord[a=41]

jshell> TRecord copy = new TRecord(r1.a());
copy ==> TRecord[a=40]

jshell> r1.equals(copy)
 ==> false

换句话说,你的记录类型违反了这个不变量,这也是你看到反序列化不一致的原因。