版本控制和 serialVersionUID

当你实现 java.io.Serializable 接口以使类可序列化时,编译器会查找 long 类型为 serialVersionUIDserialVersionUID 字段。如果类没有明确声明该字段,那么编译器将创建一个这样的字段并为其赋予一个值,该值来自 serialVersionUID 的依赖于实现的计算。此计算取决于类的各个方面,并遵循 Sun 提供的对象序列化规范 。但是,并不保证所有编译器实现的值都相同。

此值用于检查类与序列化的兼容性,这是在对已保存对象进行反序列化时完成的。Serialization Runtime 验证 serialVersionUID 从反序列化数据中读取,并且类中声明的 serialVersionUID 完全相同。如果不是这样的话,它就会引发一场骚乱。

强烈建议你明确声明并初始化 long 类型的静态最终字段,并在你想要制作 Serializable 的所有类中命名为’serialVersionUID’,而不是依赖于该字段值的默认计算,即使你不会使用版本控制。 ‘serialVersionUID’计算非常敏感,并且可能因编译器实现而异,因此你可能会因为你在序列化过程的发送方和接收方端使用不同的编译器实现而在相同的类中获得 InvalidClassException

public class Example implements Serializable {          
    static final long serialVersionUID = 1L /*or some other value*/;
    //...
}

只要 serialVersionUID 是相同的,Java Serialization 就可以处理不同版本的类。兼容和不兼容的变化是;

兼容的变化

  • 添加字段: 当正在重构的类具有未在流中出现的字段时,对象中的该字段将初始化为其类型的默认值。如果需要特定于类的初始化,则该类可以提供 readObject 方法,该方法可以将字段初始化为非默认值。
  • 添加类: 流将包含流中每个对象的类型层次结构。将流中的此层次结构与当前类进行比较可以检测其他类。由于流中没有信息来初始化对象,因此类的字段将初始化为默认值。
  • 删除类: 将流中的类层次结构与当前类的类层次结构进行比较可以检测到类已被删除。在这种情况下,从流中读取与该类对应的字段和对象。原始字段将被丢弃,但已创建已删除类引用的对象,因为它们稍后可能会在流中引用。当流被垃圾收集或重置时,它们将被垃圾收集。
  • 添加 writeObject / readObject 方法: 如果读取流的版本具有这些方法,则通常需要 readObject 读取默认序列化写入流所需的数据。它应该在读取任何可选数据之前先调用 defaultReadObject。期望 writeObject 方法像往常一样调用 defaultWriteObject 来写入所需的数据,然后可以编写可选数据。
  • 添加 java.io.Serializable: 这相当于添加类型。此类的流中没有值,因此其字段将初始化为默认值。对非可序列化类的子类化的支持要求类的超类型具有无参数构造函数,并且类本身将被初始化为默认值。如果 no-arg 构造函数不可用,则抛出 InvalidClassException。
  • 更改对字段的访问权限 访问修饰符 public,package,protected 和 private 不会影响序列化为字段分配值的能力。
  • 将字段从静态更改为非静态或瞬态更改为非瞬态: 当依赖默认序列化来计算可序列化字段时,此更改等同于向类添加字段。新字段将写入流,但较早的类将忽略该值,因为序列化不会将值分配给静态或瞬态字段。

不兼容的变化

  • 删除字段: 如果在类中删除了某个字段,则写入的流将不包含其值。当流被较早的类读取时,该字段的值将设置为默认值,因为流中没有可用的值。但是,此默认值可能会对早期版本履行合同的能力产生不利影响。
  • 在层次结构中向上或向下移动类: 由于流中的数据以错误的顺序出现,因此不允许这样做。
  • 将非静态字段更改为静态字段或非瞬态字段更改为瞬态: 依赖于默认序列化时,此更改等同于从类中删除字段。此类版本不会将该数据写入流中,因此该类的早期版本无法读取该数据。与删除字段时一样,早期版本的字段将初始化为默认值,这可能导致类以意外方式失败。
  • 更改原始字段的声明类型: 类的每个版本都使用其声明的类型写入数据。尝试读取该字段的类的早期版本将失败,因为流中的数据类型与字段的类型不匹配。
  • 更改 writeObject 或 readObject 方法,使其不再写入或读取默认字段数据或更改它,以便它尝试写入或在先前版本没有时读取它。默认字段数据必须始终显示或不显示在流中。
  • 将类从 Serializable 更改为 Externalizable 或反之亦然是一种不兼容的更改,因为流将包含与可用类的实现不兼容的数据。
  • 将类从非枚举类型更改为枚举类型,反之亦然,因为流将包含与可用类的实现不兼容的数据。
  • 删除 Serializable 或 Externalizable 是一种不兼容的更改,因为在编写时它将不再提供该类的旧版本所需的字段。
  • 如果行为会产生与该类的任何旧版本不兼容的对象,则将 writeReplace 或 readResolve 方法添加到类中是不兼容的。