Java序列化 - java.io.InvalidClassException本地类不兼容。

65

我有一个公共类,实现了Serializable接口,并被多个其他类继承。 只有这些子类被序列化过 - 从未序列化过超类。

超类已定义了一个serialVersionUID。

我不确定是否重要,但它没有被标记为private,而是默认受保护的 - 可以说它是包级别保护的。

static final long serialVersionUID = -7588980448693010399L;

超类及其子类中均未实现readObject或writeObject方法,而且子类中也没有明确定义的serialVersionUID。我认为在超类中定义一个将足以满足要求。

尽管如此,在超类中添加了一个新的实例变量(List/ArrayList)和一个新的方法,同时在其中一个子类中添加了一些私有实例变量后,读取以前序列化的对象时出现了问题。

现在在尝试读取以前序列化的对象时,会抛出异常。与此类似的异常如下所示:

com.SomeCompany.SomeSubClass; local class incompatible: stream classdesc serialVersionUID = 1597316331807173261, local class serialVersionUID = -3344057582987646196

我猜测这是因为默认的serialVersionUID已经改变了,因为在任何子类中都没有声明一个,这是由于超类和一个子类的更改导致的。

如果有建议如何解决这个问题,将不胜感激。我猜想我需要实现readObject和writeObject,但除了调用defaultReadObject()和defaultWriteObject()之外,我并不确定需要做什么。我也不知道是否需要为所有子类添加serialVerisonUIDs,或者每个子类是否需要实现readObject和writeObject,或者是否可以只在超类中实现它们(如果需要的话)。


请参考以下链接:https://dev59.com/82kv5IYBdhLWcg3wvDXq - belka
如果将来有人遇到问题: 只需删除项目中存在的.obj文件,然后再次尝试运行该项目即可。 - Sudeep Acharya
4个回答

50

@DanielChapman 给出了关于 serialVersionUID 的良好解释,但没有解决方案。解决方案是这样的:在所有 旧的 类上运行 serialver 程序。将这些 serialVersionUID 值放入您当前版本的类中。只要当前类与旧版本兼容,则应该没问题。(未来代码的注意事项:所有实现了 Serializable 接口的类都应该始终有一个 serialVersionUID)

如果新版本与旧版本兼容,则需要使用自定义的 readObject 实现执行某些操作(仅当您尝试编写与旧代码兼容的类数据时才需要自定义的 writeObject)。一般来说,添加或删除类字段不会使类串行不兼容。而更改现有字段的类型通常会造成不兼容。

当然,即使新类串行兼容的,您仍然可能需要自定义的 readObject 实现。例如,如果您想填充从旧版本类保存的数据中缺少的任何新字段(例如,您具有要在加载旧类数据时初始化为空列表的新 List 字段)。


6
如果你没有旧的类,可以从异常消息中获取旧的serialVersionUID,根据需要重复操作。 - Paul Jackson
你的回答和 truthealitiy 的回答结合起来包含了所有相关细节。也许这些问题可以合并一下? - jpaugh

32

简单来说,如果您没有指定,那么串行ID是通过哈希计算得出的。(静态成员不会被继承--它们是静态的,只有(1)个,并且属于该类)。

http://docs.oracle.com/javase/6/docs/platform/serialization/spec/class.html

getSerialVersionUID方法返回此类的serialVersionUID。请参阅第4.6节“流唯一标识符”。如果未由类指定,则返回的值是使用美国国家标准局定义的安全哈希算法(SHA)从类的名称、接口、方法和字段计算的哈希值。
如果更改类或其层次结构,则哈希值将不同。这是一件好事。现在,它们具有不同的成员,因此,如果您从其序列化形式中读取它,实际上是一个不同的对象-因此会出现异常。
长答案是,序列化非常有用,但除非没有其他方法,否则可能不应用于持久性。这是一条危险的道路,特别是因为您正在经历的情况。对于纯Java项目,您应该考虑使用数据库、XML、文件格式和可能的JPA或其他持久性结构。

20

对我来说,我忘了添加默认的序列号。

private static final long serialVersionUID = 1L;

+1;原则上,在 Netbeans 下创建新的“实体类”文件时,它会自动添加。 - Omar

8

这对我有用:

如果您将序列化的类对象写入文件,然后对文件进行了一些更改并编译了它,然后尝试读取对象,那么就会发生这种情况。

因此,如果修改和重新编译了某个类,则需要再次将必要的对象写入文件。

PS:这不是解决方案;本意是一个变通方法。


不需要这样做,也不需要丢失所有现有的序列化。请参考其他答案。 - user207421
这只是一个解决方法,而不是一个解决方案。 - skrtbhtngr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接