Java Record 的一些思考 - 序列化相关

本文探讨了Java Record类型的序列化特性,包括其简化序列化过程的原因,如何处理字段增减和类型变化,以及主流序列化框架如Jackson、Kryo和XStream的Record兼容性实现。
摘要由CSDN通过智能技术生成

Java Record 序列化相关

Record 在设计之初,就是为了找寻一种纯表示数据的类型载体。Java 的 class 现在经过不断的迭代做功能加法,用法已经非常复杂,各种语法糖,各种多态构造器,各种继承设计导致针对 Java 的序列化框架也做得非常复杂,要考虑的情况有很多很多。每次 Java 升级,如果对类结构有做改动或者加入了新特性,那么序列化框架就都需要改来兼容。这样会阻碍 Java 的发展,于是设计出了 Record 这个专门用来存储数据的类型。

经过上一节的分析我们知道,Record 类型声明后就是 final 的,在编译后,根据 Record 源码插入相关域与方法的字节码,包括:

  1. 自动生成的 private final field
  2. 自动生成的全属性构造器
  3. 自动生成的 public getter 方法
  4. 自动生成的 hashCode(),equals(),toString() 方法:
  5. 从字节码可以看出,这三个方法的底层实现是 invokeDynamic 另一个方法
  6. 调用的是 ObjectMethods.java 这个类中的 bootstrap 方法

里面的所有元素都是不可变的,这样对序列化来讲方便了很多,省略掉很多要考虑的因素,比如字段父子类继承与覆盖等等。序列化一个 Record,只需要关注这个 Record 本身,将其中的所有 field 读取出来即可,并且这些 field 都是 final 的反序列化的时候,仅通过 Record 的规范构造函数(canonical constructor)即给全属性赋值的构造函数。

接下来我们通过一个简单的例子来看下 Record 与普通类的序列化区别。

我们在这里使用了 lombok 简化代码,假设有 UserClass

@Data
public class UserClass implements Serializable {
	private final int id;
	private final int age;
}
复制代码

还有与它有相同 field 的 UserRecord

public record UserRecord(int id, int age) implements Serializable {}
复制代码

编写使用 Java 原生序列化的代码:

public class SerializationTest {
	public static void main(String[] args) throws Exception {
		try (FileOutputStream fileOutputStream = new  FileOutputStream("data");
			 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
			//先写入 UserClass
			objectOutputStream.writeObject(new UserClass(1, -1));
			//再写入 UserRecord
			objectOutputStream.writeObject(new UserRecord(2, -1));
		}
	}
}
复制代码

执行,将两个对象写入了文件 data 中,然后,再编写代码从这个文件中读取出来并输出:

public class DeSerializationTest {
	public static void main(String[] args) throws Exception {
		try (FileInputStream fileInputStream = new  FileInputStream("data");
			 ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
			//读取 UserClass
			System.out.println(objectInputStream.readObject());
			//读取 UserRecord
			System.out.println(objectInputStream.readObject());
		}
	}
}
复制代码

执行后,会看到输出:

UserClass(id=1, age=-1)
UserRecord[id=1, age=-1]
复制代码

构造器测试

接下来,我们修改下源码,在 UserClass 和 UserRecord 中增加 id 和 age 都不能小于 1 的判断。并且,额外给 UserRecord 增加一个构造器,来验证反序列化使用的是 UserRecord 全属性构造器。

@Data
public class UserClass implements Serializable {
	private final int id;
	private final int age;

	public UserClass(int id, int age) {
		if (id < 0 || age < 0) {
			throw new IllegalArgumentException("id and age should be larger than 0");
		}
		this.id = id;
		this.age = age;
	}
}
public record UserRecord(int id, int age) implements Serializable {
	public UserRecord {
		if (id < 0 || age < 0) {
			throw new IllegalArgumentException("id and age should be larger than 0");
		}
	}

	public UserRecord(int id) {
		this(id, 0);
	}
}
复制代码

再次执行代码 DeSerializationTest,我们会发现有报错,但是 UserClass 被反序列化出来了:

UserClass(id=1, age=-1)
Exception in thread "main" java.io.InvalidObjectException: id and age should be larger than 0
	at java.base/java.io.ObjectInputStream.readRecord(ObjectInputStream.java:2348)
	at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2236)
	at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1742)
	at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:514)
	at java.base/java.io.Objec
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值