当缺少serialVersionUID时,Eclipse会发出警告。
可序列化类Foo未声明静态finallong类型的serialVersionUID字段
什么是serialVersionUID,为什么它很重要?请显示缺少serialVersionUID将导致问题的示例。
当缺少serialVersionUID时,Eclipse会发出警告。
可序列化类Foo未声明静态finallong类型的serialVersionUID字段
什么是serialVersionUID,为什么它很重要?请显示缺少serialVersionUID将导致问题的示例。
当前回答
serialVersionUID有助于序列化数据的版本控制。序列化时,其值与数据一起存储。反序列化时,将检查同一版本,以查看序列化数据与当前代码的匹配情况。
如果要对数据进行版本化,通常从serialVersionUID 0开始,并将其与更改序列化数据(添加或删除非瞬时字段)的类的每一个结构更改一起转储。
内置的反序列化机制(在.defaultReadObject()中)将拒绝从旧版本的数据进行反序列化。但如果您愿意,您可以定义自己的readObject()函数,该函数可以读取旧数据。然后,此自定义代码可以检查serialVersionUID,以了解数据的版本,并决定如何对其进行反序列化。如果存储的序列化数据在代码的几个版本中都存在,则此版本控制技术非常有用。
但将序列化数据存储如此长的时间跨度并不常见。更常见的是使用串行化机制将数据临时写入例如缓存,或通过网络将数据发送到具有相同版本的代码库相关部分的另一个程序。
在这种情况下,您对保持向后兼容性不感兴趣。您只关心确保正在通信的代码库确实具有相同版本的相关类。为了方便这种检查,您必须像以前一样维护serialVersionUID,并且在对类进行更改时不要忘记更新它。
如果忘记更新字段,则可能会导致一个类的两个不同版本具有不同的结构,但具有相同的serialVersionUID。如果发生这种情况,默认机制(在.defaultReadObject()中)将检测不到任何差异,并尝试对不兼容的数据进行反序列化。现在,您可能会遇到一个神秘的运行时错误或静默失败(空字段)。这些类型的错误可能很难找到。
因此,为了帮助这个用例,Java平台为您提供了不手动设置serialVersionUID的选择。相反,类结构的哈希将在编译时生成并用作id。该机制将确保您永远不会有具有相同id的不同类结构,因此您不会得到上述难以跟踪的运行时序列化失败。
但自动生成id策略也有其背后的原因。也就是说,为同一类生成的id在编译器之间可能会有所不同(正如Jon Skeet所提到的)。因此,如果在使用不同编译器编译的代码之间传递序列化数据,建议无论如何都手动维护id。
如果您像前面提到的第一个用例那样与数据向后兼容,那么您可能也希望自己维护id。这是为了获得可读的id,并更好地控制它们的更改时间和方式。
其他回答
要理解字段serialVersionUID的重要性,应该了解序列化/反序列化的工作原理。
当序列化一个Serializable类对象时,Java Runtime将一个序列版本号(称为serialVersionUID)与这个序列化对象相关联。在反序列化此序列化对象时,Java Runtime将序列化对象的serialVersionUID与类的serialVersion UID匹配。如果两者都相等,则只有它继续执行进一步的反序列化过程,否则抛出InvalidClassException。
因此,我们得出结论,要使序列化/反序列化过程成功,序列化对象的serialVersionUID必须与类的serialVersion UID等效。如果程序员在程序中显式指定serialVersionUID值,那么无论序列化和反序列化平台如何,相同的值都将与序列化对象和类相关联(例如,可以使用sun或MS JVM在类似windows的平台上进行序列化,而反序列化可以使用Zing JVM在不同的平台Linux上进行)。
但是,如果程序员未指定serialVersionUID,则在对任何对象进行Serialization\DeSerialization时,Java运行时会使用自己的算法来计算它。这种serialVersionUID计算算法因JRE而异。对象序列化的环境也可能使用一个JRE(例如:SUN JVM),而取消序列化的环境则使用LinuxJvm(zing)。在这种情况下,与序列化对象关联的serialVersionUID将不同于在取消序列化环境中计算的类的serialVersion UID。反过来,反序列化将不会成功。所以为了避免这种情况/问题,程序员必须始终指定Serializable类的serialVersionUID。
首先回答您的问题,当我们不在类中声明SerialVersionUID时,Java运行时会为我们生成它,但该过程对许多类元数据敏感,包括字段数、字段类型、字段的访问修饰符、类实现的接口等。因此,建议我们自己声明它,Eclipse也会警告您。
序列化:我们经常处理状态(对象变量中的数据)非常重要的重要对象,因此在将对象状态发送到其他机器时,我们不会因电源/系统故障(或网络故障)而丢失它。这个问题的解决方案被命名为“持久性”,这意味着持久化(保存/保存)数据。串行化是实现持久性的许多其他方法之一(通过将数据保存到磁盘/内存)。保存对象的状态时,为对象创建标识,以便能够正确地将其读回(反序列化),这一点非常重要。此唯一标识ID为SerialVersionUID。
每次序列化对象时,对象都会标记对象类的版本ID号。此ID称为serialVersionUID,它是根据有关类结构的信息计算的。假设您创建了一个Employee类,它的版本id为#333(由JVM分配),现在当您将序列化该类的对象(假设Employees对象)时,JVM将为其分配UID为#333。
考虑一种情况——将来您需要编辑或更改类,在这种情况下,当您修改它时,JVM将为它分配一个新的UID(假设#444)。现在,当您尝试反序列化雇员对象时,JVM会将序列化对象(雇员对象)的版本ID(#333)与类的版本ID进行比较,即#444(自更改以来)。相比之下,JVM将发现两个版本UID不同,因此反序列化将失败。因此,如果每个类的serialVersionID由程序员自己定义。即使类在未来演变,它也将是相同的,因此JVM将始终发现类与序列化对象兼容,即使类已更改。有关更多信息,请参阅HEAD FIRST JAVA的第14章。
如果您在一个从未想过序列化的类上收到此警告,并且您没有声明自己实现了Serializable,这通常是因为您继承了一个实现Serializable的超类。通常情况下,最好委托给这样的对象,而不是使用继承。
所以
public class MyExample extends ArrayList<String> {
public MyExample() {
super();
}
...
}
do
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方法。我认为在这种情况下不应该有警告(因为你通常无法可靠地序列化和反序列化这些匿名类,不管是在你的类的不同版本中),但我不确定编译器如何识别这一点。
不用担心,默认计算非常好,可以满足999999%的情况。如果你遇到问题,你可以——正如已经说过的——在需要时引入UID(这是极不可能的)