Java Record 序列化相关
Record 在设计之初,就是为了找寻一种纯表示数据的类型载体。Java 的 class 现在经过不断的迭代做功能加法,用法已经非常复杂,各种语法糖,各种多态构造器,各种继承设计导致针对 Java 的序列化框架也做得非常复杂,要考虑的情况有很多很多。每次 Java 升级,如果对类结构有做改动或者加入了新特性,那么序列化框架就都需要改来兼容。这样会阻碍 Java 的发展,于是设计出了 Record 这个专门用来存储数据的类型。
经过上一节的分析我们知道,Record 类型声明后就是 final 的,在编译后,根据 Record 源码插入相关域与方法的字节码,包括:
- 自动生成的 private final field
- 自动生成的全属性构造器
- 自动生成的 public getter 方法
- 自动生成的 hashCode(),equals(),toString() 方法:
- 从字节码可以看出,这三个方法的底层实现是 invokeDynamic 另一个方法
- 调用的是
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.ObjectInputStream.readObject(ObjectInputStream.java:472) at DeSerializationTest.main(DeSerializationTest.java:13) Caused by: java.lang.IllegalArgumentException: id and age should be larger than 0 at UserRecord.<init>(UserRecord.java:6) at java.base/java.io.ObjectInputStream.readRecord(ObjectInputStream.java:2346) ... 5 more
兼容性测试
我们再来看如果删除一个字段会怎么样:
@Data public class UserClass implements Serializable { private final int age; } public record UserRecord(int age) implements Serializable { }
执行代码,读取 UserClass 的时候就会报错,这也是符合预期的,因为这在普通类对象的反序列化说明中就说这种是不兼容修改。将 UserClass 的字段恢复,重新执行代码,发现成功:
UserClass(id=1, age=-1) UserRecord[age=-1]
也就是说,Record 是默认兼容缺失字段的反序列化的
我们将字段恢复,再来看多一个字段会怎么样:
@Data public class UserClass implements Serializable { private final int id; private final int sex; private final int age; } public record UserRecord(int id, int sex, int age) implements Serializable { }
执行代码,读取 UserClass 的时候就会报错,这也是符合预期的。将 UserClass 的字段恢复,重新执行代码,发现成功:
UserClass(id=1, age=-1) UserRecord[id=2, sex=0, age=-1]
也就是说,Record 是默认兼容字段变多的反序列化的
最后测试一下 Record 的 field 类型如果变了呢:
public record UserRecord(int id, Integer age) implements Serializable { }
执行代码发现失败,因为类型不匹配了(就算是包装类也不行):
UserClass(id=1, age=-1) Exception in thread "main" java.io.InvalidClassException: UserRecord; incompatible types for field age at java.base/java.io.ObjectStreamClass.matchFields(ObjectStreamClass.java:2391) at java.base/java.io.ObjectStreamClass.getReflector(ObjectStreamClass.java:2286) at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:788) at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2060) at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1907) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2209) 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.ObjectInputStream.readObject(ObjectInputStream.java:472) at DeSerializationTest.main(DeSerializationTest.java:13)