java - 什么是serialVersionUID,而我为什么要使用它吗?

  显示原文与译文双语对照的内容

Eclipse 在缺少 serialVersionUID 时发出警告。

可以序列化类Foo不声明类型为long的静态 final serialVersionUID字段

什么是 serialVersionUID,为什么它很重要? 请显示缺少 serialVersionUID 会导致问题的示例。

时间:

java.io.Serializable的文档可能与你将得到的解释一样好:

序列化运行时与每个可以序列化类关联一个版本号,称为 serialVersionUID,它在反序列化过程中使用,以验证序列化对象的发送者和接收者是否已经加载了与序列化兼容的对象的类。 如果接收者为对象加载了一个类,该类的对象与对应的发送者的类不同,则反序列化将导致 InvalidClassException 。 可以序列化类可以通过声明一个名为" serialVersionUID"的字段显式声明它自己的serialVersionUID,该字段必须是静态的final 和类型 long:


ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果可以序列化类没有显式声明 serialVersionUID,则序列化运行时将根据类的各个方面计算该类的默认serialVersionUID值,如 Java(TM) 对象序列化规范中所述。 然而在意外的InvalidClassExceptions deserialization,期间,它是强烈推荐所有所有可以序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算是相当易类的内容,它可能会随编译器的实现,并可以因此 result. 因此,为了在不同的java编译器实现之间保证一致的serialVersionUID值,可以序列化类必须声明显式的serialVersionUID值。 还强烈建议显式的serialVersionUID声明使用私有修饰符,因为这样的声明只适用于立即声明class--serialVersionUID字段的声明,而不是继承的成员。

如果你只是为了实现( 如果你对HTTPSession进行序列化,那么谁会在意。例如如果它被存储,你可能不关心de-serializing表单对象) 而序列化,那么你可以忽略这个。

如果你正在使用序列化,那么只有在直接使用序列化存储和检索对象的情况下,才会有问题。 serialVersionUID表示类的版本,如果类的当前版本与以前的版本不兼容,则应该增加它。

大多数时候,你可能不会直接使用序列化。 如果是这样,通过单击quick快速修复fix选项生成一个默认的可以序列化 uid,而不用担心它。

我无法放弃这个机会来插入 Josh Effective Java ( 2和版本) 。 第 11章是Java序列化中不可或缺的资源。

根据 Josh,automatically-generated UID基于类名称,实现的接口和所有公共和受保护的成员生成。 以任何方式将改变serialversionuid,更改任何这些朗读 因此,你不需要只在确定类的一个版本将被序列化为( 跨进程或者以后从存储中检索)的情况下进行混乱。

如果你可以暂时忽略它们,并在以后发现你需要以某种方式改变了一个类,但保持兼容性白/旧serialver在JDK版本的类,则可以使用工具来生成serialVersionUID的旧类,并显式地设置上的在新的类中。 ( 根据你的更改,你可能还需要通过添加 writeObjectreadObject 方法实现自定义序列化- 参见 Serializable javadoc或者前面的章节 11.)

你可以告诉 Eclipse 忽略这些serialVersionUID警告警告:

窗口> 首选项> Java> 编译器> 错误/警告> 潜在编程问题

如果你不知道,你还可以在本节中启用许多其他警告,很多是非常有用的:

  • 潜在的编程问题:可能的意外布尔赋值
  • 潜在的编程问题:空指针访问
  • 不需要的代码:局部变量从不被读取
  • 不必要的代码:冗余空检查
  • 不必要的代码:不必要的类型转换或者'instanceof''

还有更多。

serialVersionUID 简化序列化数据的版本控制。 序列化时,它的值与数据一起存储。 当de-serializing时,检查相同的版本以查看序列化数据与当前代码的匹配方式。

如果你想你的数据时,你通常首先从一个版本的0 serialVersionUID 再扭一下它与各个结构更改添加到类这将修改序列化数据( 添加或者删除non-transient字段) 。

内置的de-serialization机制( in.defaultReadObject() ) 将拒绝从旧版本的数据中 de-serialize 。 但是如果你想你可以定义你自己的readObject() -function,它可以读取旧数据。 然后,这个自定义代码可以检查 serialVersionUID,以了解数据的版本,并决定如何 de-serialize 。 如果存储的序列化数据保存在代码的多个版本中,那么这种版本控制技术很有用。

但是,在这么长的时间内存储序列化数据并不常见。 它是一个缓存非常通常总是将序列化机制来临时数据写入规则的实例或者通过网络将它的发送到另一个程序使用同一版本的相关的的部分代码基。

在这种情况下,你对保持向后兼容性不感兴趣。 你只关心确保正在通信的代码库具有相同的相关类版本。 为了便于这样的检查,你必须像以前一样维护 serialVersionUID,并且在更改类时不要忘记更新。

如果忘记更新字段,你可能会得到具有不同结构但具有相同 serialVersionUID的类的两个不同版本。 如果发生这种情况,默认的机制( in.defaultReadObject() ) 将不会检测到任何差异,并尝试de-serialize不兼容的数据。 现在你可能会遇到一个神秘的运行时错误或者静默失败( 空字段) 。 这些类型的错误可能很难找到。

所以为了帮助这个用例,Java平台提供了一个不手动设置 serialVersionUID的选择。 相反,类结构的哈希将在compile-time处生成并用作标识。 这里机制将确保你永远不会有具有相同标识的不同类结构,因此你不会得到上述hard-to-trace运行时序列化失败。

但是auto-generated标识策略有一个背面。 即同一类的生成的标识可能在编译器的( 就像上面 Jon Skeet提到的) 之间不同。 因此,如果你在用不同编译器编译的代码之间通信序列化数据,那么建议手工维护 ids 。

如果你是 backwards-compatible,并且你的数据像前面提到的第一个用例一样,你也可能想自己维护这个标识。 为了获得可读的ids,并且对它们什么时候以及如何改变有更好的控制。

什么是,为什么要使用它?

SerialVersionUID 是每个类的唯一标识符,JVM 使用它来比较类的版本,确保在反序列化期间在序列化期间使用相同的类。

指定一个会提供更多的控制,但是如果你不指定,JVM会生成一个。 生成的值在不同的编译器之间可能不同。 此外,有时你只需要出于某种原因禁止反序列化旧序列化对象 [ backward incompatibility ],在这种情况下,你只需要更改。

的Java文档表示:

"默认的serialVersionUID计算对类细节高度敏感,这可能会因编译器实现而有所不同,因此在反序列化过程中可能会导致意外的InvalidClassExceptions"。

你必须声明 serialVersionUID ,因为它给我们提供了更多的控制信息。

本文对主题有一些优点。

在一个超类,这个类实现serializable,如果你在类上收到这里警告你不要有没有想过要序列化,而这个你没有声明自己 implements Serializable,往往是因为你 inherited. 通常,最好委托给这样一个对象而不是使用继承。

所以,而不是


public class MyExample extends ArrayList<String> {

 public MyExample() {
 super();
 }
. . .
}


public class MyExample {
 private List<String> myList;

 public MyExample() {
 this.myList = new ArrayList<String>();
 }
. . .
}

在相关方法中调用 myList.foo() 而不是 this.foo() ( 或者 super.foo() ) 。 ( 这在所有情况下都不适用,但仍然很常见。)

我经常看到人们扩展 JFrame,或者当他们真的只需要委托给这个。 ( 这也有助于auto-completing中的,因为JFrame有数百个方法,当你想在类上调用自定义的方法时,你不需要它。)

警告( 或者 serialVersionUID 。) 不可避免的情况是当你从AbstractAction扩展时,通常在匿名类中,只添加 actionPerformed-method 。 我认为这里不应该有一个警告,( 因为你通常不能可靠地序列化和反序列化此类匿名类,所以accross不支持类的不同版本),但是我不知道编译器如何识别这个。

最初的问题要求'为什么它重要'和'范例',这将是有用的。 我找到了一个。

假设你创建一个 Car 类,实例化它,并将它写出到一个对象流。 flattened的汽车对象位于文件系统中一段时间。 同时,如果通过添加新字段修改了 Car 类。 稍后,当你尝试读取时( 例如 。 反序列化) 展开的Car 对象,你得到了 java.io.InvalidClassException - 因为所有可以序列化类都自动给出唯一的标识符。 当类的标识符不等于展平对象的标识符时,抛出这里异常。 如果你真的考虑了,则抛出异常是因为添加了新字段。 你可以避免这种异常被抛出自己通过控制版本控制通过声明一个明确的serialVersionUID 。 显式声明 serialVersionUID ( 因为不必计算) 也有一个小的性能优势。 因此,最好在创建可以序列化类时将自己的serialVersionUID添加到可以序列化类,如下所示:


public class Car {
static final long serialVersionUID = 1L;//assign a long value
}

如果你永远不需要将对象序列化为字节数组并发送/存储它们,那么你就不必担心它。 如果你这样做,那么你必须考虑你的serialVersionUID,因为对象的解串器将与它的类加载器的版本相匹配。 在Java语言规范中阅读更多关于它的内容。

于 99,9999相关 % 很管用和 suffice. cases,不用麻烦,默认的计算 如果你遇到了问题,你可以-如前所述-介绍截至uid需要arrise(这是极不可能)

...