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

简介: Java Record 的一些思考 - 序列化相关(上)

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.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)
相关文章
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
18天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
57 5
|
20天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 3
|
23天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
1月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
1月前
|
消息中间件 存储 Java
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
大数据-58 Kafka 高级特性 消息发送02-自定义序列化器、自定义分区器 Java代码实现
44 3
|
1月前
|
分布式计算 资源调度 Hadoop
Hadoop-10-HDFS集群 Java实现MapReduce WordCount计算 Hadoop序列化 编写Mapper和Reducer和Driver 附带POM 详细代码 图文等内容
Hadoop-10-HDFS集群 Java实现MapReduce WordCount计算 Hadoop序列化 编写Mapper和Reducer和Driver 附带POM 详细代码 图文等内容
91 3
|
1月前
|
Java 数据库 对象存储
Java 序列化详解
本文详细解析了Java序列化的概念与应用。通过具体实例,深入探讨了其在对象存储和传输中的作用及实现方法,帮助读者理解如何有效利用这一特性来简化数据交换,并对其实现机制有了更深入的认识。
|
22天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
22 0