当a时,Eclipse会发出警告 serialVersionUID
不见了。
可序列化类Foo不声明静态final serial类型为long的serialVersionUID字段
什么是 serialVersionUID
为什么这很重要?请显示遗失的示例 serialVersionUID
会引起问题。
当a时,Eclipse会发出警告 serialVersionUID
不见了。
可序列化类Foo不声明静态final serial类型为long的serialVersionUID字段
什么是 serialVersionUID
为什么这很重要?请显示遗失的示例 serialVersionUID
会引起问题。
的文档 java.io.Serializable
可能与你得到的解释一样好:
序列化运行时关联 每个可序列化的类都有一个版本 number,称为serialVersionUID, 在反序列化期间使用 验证发件人和收件人 已加载序列化对象 该对象的类 兼容 序列化。如果接收者有 为具有的对象加载了一个类 与此不同的serialVersionUID 相应发件人的班级, 然后反序列化将导致
InvalidClassException
。可序列化的 class可以声明自己的 serialVersionUID显式地由 声明一个名为的字段 “serialVersionUID
“那一定是 静态,最终和类型long
:ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果一个 serializable类没有显式 声明一个serialVersionUID,然后是 序列化运行时将计算出一个 默认的serialVersionUID值 该课程基于各个方面 这个班,如中所述 Java(TM)对象序列化 规范。但是,确实如此 非常 推荐的 所有可序列化的 类明确声明 serialVersionUID值,因为 默认的serialVersionUID计算 对课程细节非常敏感 可能因编译器而异 实现,因此可以得到 出乎意料的
InvalidClassExceptions
在反序列化期间。因此,来 保证一致 serialVersionUID值 不同的java编译器 实现,可序列化的类 必须声明一个明确的 serialVersionUID值。也是 强烈建议明确 serialVersionUID声明使用 私有修饰符,如果可能的话,因为 此类声明仅适用于 立刻宣布 class - serialVersionUID字段不是 作为继承成员有用。
如果你的序列化只是因为你必须为了实现而序列化(谁关心你是否为HTTPSession序列化,例如......如果它存储与否,你可能不关心反序列化表单对象)那么你可以忽略这个。
如果您实际使用的是序列化,那么只有在您计划直接使用序列化存储和检索对象时才有意义。 serialVersionUID表示您的类版本,如果您的类的当前版本与其先前版本不向后兼容,则应增加它。
大多数情况下,您可能不会直接使用序列化。如果是这种情况,请单击快速修复选项生成默认的可序列化uid,不要担心。
我不能错过这个机会来插入Josh Bloch的书 有效的Java (第2版)。第11章是Java序列化不可或缺的资源。
根据Josh,自动生成的UID是基于类名,已实现的接口以及所有公共成员和受保护成员生成的。以任何方式改变任何这些将改变 serialVersionUID
。因此,只有当您确定不会将一个以上版本的类序列化时(无论是跨进程还是稍后从存储中检索),您都不需要弄乱它们。
如果你现在忽略它们,并且稍后发现你需要以某种方式更改类但是保持兼容性与旧版本的类,你可以使用JDK工具 的serialver 生成 serialVersionUID
在...上 旧 class,并在新类上显式设置。 (根据您的更改,您可能还需要通过添加来实现自定义序列化 writeObject
和 readObject
方法 - 见 Serializable
javadoc或上述章节11.)
您可以告诉Eclipse忽略这些serialVersionUID警告:
窗口>首选项> Java>编译器>错误/警告>潜在的编程问题
如果你不知道,你可以在本节中启用很多其他警告(甚至有一些报告为错误),许多非常有用:
还有很多。
serialVersionUID
便于序列化数据的版本控制。序列化时,其值与数据一起存储。反序列化时,将检查相同的版本以查看序列化数据如何与当前代码匹配。
如果要对数据进行版本控制,通常以a开头 serialVersionUID
为0,并将其与您的类的每个结构更改碰撞,这会改变序列化数据(添加或删除非瞬态字段)。
内置的反序列化机制(in.defaultReadObject()
)将拒绝从旧版本的数据中反序列化。但如果你想,你可以定义自己的 的readObject()功能,可以读回旧数据。然后,此自定义代码可以检查 serialVersionUID
以便知道数据所在的版本并决定如何对其进行反序列化。如果您存储的代码化数据能够存储多个版本的代码,则此版本控制技术非常有用。
但是,如此长时间存储序列化数据并不常见。使用序列化机制将数据临时写入例如缓存或通过网络将其发送到具有相同版本的代码库相关部分的另一个程序更为常见。
在这种情况下,您不想保持向后兼容性。您只关心确保通信的代码库确实具有相同类型的相关类。为了方便这样的检查,你必须保持 serialVersionUID
就像以前一样,在更改类时不要忘记更新它。
如果您忘记更新该字段,最终可能会得到两个不同版本的类,这些类具有不同的结构但具有相同的结构 serialVersionUID
。如果发生这种情况,则默认机制(in.defaultReadObject()
)不会检测到任何差异,并尝试反序列化不兼容的数据。现在,您最终可能会遇到神秘的运行时错误或静默失败(空字段)。可能很难找到这些类型的错误。
因此,为了帮助这个用例,Java平台为您提供了不设置的选择 serialVersionUID
手动。相反,类结构的哈希将在编译时生成并用作id。此机制将确保您永远不会有具有相同ID的不同类结构,因此您将无法获得上面提到的这些难以跟踪的运行时序列化失败。
但是自动生成的id策略有一个缺点。也就是说,同一类的生成的id可能在编译器之间有所不同(如上面的Jon Skeet所述)。因此,如果您在使用不同编译器编译的代码之间传递序列化数据,则建议手动维护ID。
如果您在提到的第一个用例中向后兼容您的数据,您也可能希望自己维护ID。这是为了获得可读的ID,并且可以更好地控制它们何时以及如何变化。
什么是 的serialVersionUID 我为什么要用呢?
SerialVersionUID
是每个类的唯一标识符, JVM
使用它来比较类的版本,确保在反序列化期间加载序列化期间使用相同的类。
指定一个可以提供更多控制,但如果您未指定,JVM会生成一个控件。生成的值可能因不同编译器而异。此外,有时你只是想出于某种原因禁止反序列化旧的序列化对象[backward incompatibility
],在这种情况下,您只需要更改serialVersionUID。
该 javadocs for Serializable
说:
默认的serialVersionUID计算对类非常敏感 细节可能因编译器实现而异,并且可以 因此导致意外
InvalidClassException
期间 反序列化。
因此,您必须声明serialVersionUID,因为它为我们提供了更多控制权。
本文 对这个话题有一些好处。
最初的问题已经问到了“为什么这很重要”和“例子” Serial Version ID
会有用的。好吧,我找到了一个。
假设您创建了一个 Car
class,实例化它,并将其写出到对象流。扁平的汽车对象在文件系统中存在一段时间。同时,如果 Car
通过添加新字段来修改类。稍后,当您尝试读取(即反序列化)扁平化时 Car
对象,你得到了 java.io.InvalidClassException
- 因为所有可序列化的类都会自动赋予唯一标识符。当类的标识符不等于展平对象的标识符时,抛出此异常。如果您真的考虑过它,则会因为添加新字段而引发异常。您可以通过声明显式serialVersionUID来自行控制版本控制来避免抛出此异常。在明确声明您的内容方面,还有一个小的性能优势 serialVersionUID
(因为不必计算)。因此,最好在创建它们时将自己的serialVersionUID添加到Serializable类中,如下所示:
public class Car {
static final long serialVersionUID = 1L; //assign a long value
}
如果你在课堂上收到这个警告,你就不会想到序列化了,而且你没有自己声明 implements Serializable
,这通常是因为你从一个超类继承而来,它实现了Serializable。通常,最好委托给这样的对象而不是使用继承。
所以,而不是
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等,当他们真的只需要委托给它时。 (这也有助于在IDE中自动完成,因为JFrame有数百种方法,当您想在课堂上调用自定义方法时,您不需要这些方法。)
警告(或serialVersionUID)不可避免的一种情况是,从AbstractAction扩展(通常在匿名类中),只添加actionPerformed-method。我认为在这种情况下不应该有警告(因为你通常无法在类的不同版本中对这些匿名类进行可靠的序列化和反序列化),但我不确定编译器如何识别它。
如果您永远不需要将对象序列化为字节数组并发送/存储它们,那么您不必担心它。如果这样做,那么你必须考虑你的serialVersionUID,因为对象的反序列化器会将它与它的类加载器具有的对象版本相匹配。在Java语言规范中阅读更多相关内容。
要理解字段serialVersionUID的重要性,应该了解序列化/反序列化的工作原理。
当Serializable类对象被序列化时,Java Runtime将序列版本号(称为serialVersionUID)与此序列化对象相关联。在反序列化此序列化对象时,Java Runtime将序列化对象的serialVersionUID与类的serialVersionUID匹配。如果两者都相等,那么只有它继续进行反序列化的进一步过程,否则抛出InvalidClassException。
因此,我们得出结论,为了使序列化/反序列化过程成功,序列化对象的serialVersionUID必须等同于类的serialVersionUID。如果程序员在程序中明确指定serialVersionUID值,那么相同的值将与序列化对象和类相关联,而不管序列化和反序列化平台(例如,序列化可以通过使用sun或类似于windows的平台来完成) MS JVM和反序列化可能位于使用Zing JVM的不同平台Linux上。
但是如果程序员没有指定serialVersionUID,那么在执行任何对象的Serialization \ DeSerialization时,Java运行时会使用自己的算法来计算它。此serialVersionUID计算算法因JRE而异。对象序列化的环境也可能是使用一个JRE(例如:SUN JVM),而反序列化的环境是使用Linux Jvm(zing)。在这种情况下,与序列化对象关联的serialVersionUID将与在反序列化环境中计算的类的serialVersionUID不同。反过来反序列化也不会成功。所以为了避免这种情况/问题,程序员必须始终指定Serializable类的serialVersionUID。
不要打扰,默认计算非常好,足以满足99,9999%的情况。如果你遇到问题,你可以 - 正如已经说明的那样 - 引入UID作为需求出现(这是非常不可能的)