记录序列化 - Java 啜饮

了解记录序列化...

Java 16 中引入的记录 (JEP 395)解决了与序列化相关的几个关键问题。这是 Java 生态系统中经常遇到的难题的根源。

透明数据载体

记录被设计为透明的数据载体。这是通过对记录施加一些约束来实现的,包括

  • 无法扩展,超类始终为 java.lang.Record
  • 无法抽象,并且隐式为 final
  • 无法声明实例字段或包含实例初始化器

因此,这使得记录的序列化和反序列化过程变得更加简单,并且完全由记录类的公共定义决定

  • 只有记录的组件可以被序列化: record Range(int high, int low)
  • 记录序列化就像调用 defaultWriteObject 一样
  • 通过调用公共规范构造函数反序列化记录
  • writeObjectreadObjectreadObjectNoDatawriteExternalreadExternalserialVersionUID 方法和字段被忽略

这与 Stuart Marks 在此处描述的类的序列化和反序列化过程形成了鲜明的对比。

在反序列化过程中尊重封装

在反序列化类时,Java 不会调用类的构造函数。这可能会导致创建“不可能的对象”,即无法通过正常的编程路径创建的对象,如下面 Range 的示例所示

public class Range implements Serializable{
	int low;
	int high;
	public Range(int low, int high) {
		if(low > high) {
			throw new IllegalArgumentException(
			String.format("high: %d must be greater than low: %d", low, high));
		}
		this.low = low;
		this.high = high;
	}
}

Range 的构造函数检查字段 low 必须小于字段 high。但是,当反序列化违反该要求的数据时,如下面的示例所示

Range [low=10, high=1]

反序列化过程成功,并使用以下值创建了一个新的 Range 实例

Range [low=10, high=1]

如果运行相同的场景,但 Range 被构建为如下所示的记录

public record Range(int low, int high) 
implements Serializable {
	public Range(int low, int high) {
		if(low > high) {
			throw new IllegalArgumentException(
			String.format("high: %d must be greater than low: %d", high, low));
		}
		this.low = low;
		this.high = high;
	}
}

相反,由于 Range 的构造函数抛出了异常 (IllegalArgumentException),因此在反序列化过程中将抛出 InvalidObjectException

Exception in thread "main" java.io.InvalidObjectException: high: 1 must be greater than low: 10

更好的模型版本控制支持

正如所有开发人员所知,变化是我们领域的一个常数,它适用于我们对领域概念建模的方式。在这里,记录再次比标准类提供了更好的模型版本控制支持。

双向兼容性

经常会向模型添加、更改和删除字段。记录在发生这种情况时提供了更好的支持。

新字段

如果向记录类添加新字段,例如向 Range 添加字段 mid,如下例所示

public record Range(int lo, int hi, int mid) implements Serializable {}

当反序列化不包含 midRange 版本时,如下所示

Range [low=1, high=10]

JVM 会自动为类型或基元注入一个默认值,在本例中为 0,并将其注入到规范构造函数中

Range [low=1, high=10, mid=0]

已删除和无法识别的字段

如果更改*或删除字段,则只会传递与记录组件匹配的流中的值。

因此,反序列化包含 mid 值的 Range 实例

Range [low=10, high=10, mid=5]

到不包含 mid 字段的 Range 版本

public record Range(int low, int high) implements Serializable {}

在反序列化过程中将忽略 mid 的值,并将创建包含这些值的 Range 实例

Range [low=1, high=10]

进一步阅读

编码愉快!