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

在这篇关于可序列化记录的文章中指出

反序列化通过调用记录类的规范构造函数来创建新的记录对象,将从流中反序列化的值作为参数传递给规范构造函数。这是安全的,因为这意味着记录类可以在将值分配给字段之前对其进行验证,就像普通 Java 程序通过 new 创建记录对象一样。“不可能”的对象是不可能的。

这与仅用于验证的构造函数争论。然而,当构造函数操作参数时,这会导致相当奇怪的行为。考虑这个非常人为的简单示例:

以下记录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]在后续运行中,因此我从反序列化中获得的状态与我在那里输入的状态不同。使用如下类似的类,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 + "]";
    }
}

所以我的问题是:是否有任何规则禁止/不鼓励将构造函数用于验证以外的任何内容(例如,我正在考虑在存储输入之前对密码进行哈希处理)?或者是否有另一种方法来反序列化对象以便恢复初始状态?

回答

如果您查看记录文档,它会显示以下内容:

对于所有记录类,以下不变量必须成立:如果记录 R 的组件是 c1、c2、... cn,那么如果记录实例被复制如下:

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

那么一定是 r.equals(copy) 的情况。

但是,您的记录类不是这种情况:

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)
$4 ==> false

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

  • There are a few good reasons why you want to change component - the most common example is doing something akin to `a = List.copyOf(a);`. This does *not* invalidate the invariant, as `a.equals(List.copyOf(a))`. (Well, if the input `List` conforms to it's contract.)
  • @JohannesKuhn Exactly. The invariant was constructed to allow this case (normalization, defensive copies.) It may be necessary to override equals to preserve the invariant (e.g., arrays defensively copied need to be compared by contents.)

以上是Java记录序列化和对规范构造函数的重复调用的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>