我有一个关于Java序列化的问题,您可能需要修改可序列化类并保持向后兼容性。
我来自深度C#经验,所以请允许我将Java与.NET进行比较。
在我的Java场景中,我需要使用Java的运行时序列化机制序列化对象,并将二进制数据存储在永久存储中以便将来重用这些对象。 问题是,将来,课程可能会发生变化。可以添加或删除字段。
我不了解Java序列化,除了这篇关于not to program in Java处理序列化时如何的精彩文章。 正如我想象的那样(d),serialVersionUID在Java序列化中起着关键作用,这是我需要你帮助的地方。
除了文章的例子(我知道它的编码很糟糕),当我在修改类后要求更新它时,该字段是否应该被修改?
我记得在.NET世界中,当我添加新字段时,我必须将[OptionalField]
属性添加到字段以获得向后兼容性,因此CLR不会在旧的序列化数据中要求它。此外,当我需要弃用字段时,我必须只删除公共方法而不是私有字段。
最佳序列化的指导原则是什么?
谢谢。
[添加]这是一个例子。假设我有Foo类
public class Foo {
private String bar;
}
然后我改为:
public class Foo {
private String bar;
private Integer eggs;
}
这两个版本之间是否兼容?如果我在编译“newFoo”时反序列化“oldFoo”,那么egg会等于null还是抛出异常?我更喜欢第一个,显然是!!
答案 0 :(得分:11)
假设您有一个类MyClass
,并且您希望确保序列化兼容性向前发展,或者至少确保您不会无意中更改其序列化表单。在大多数情况下,您可以使用GS Collections test utilities中的Verify.assertSerializedForm()
。
首先编写一个测试,声明您的类的serialVersionUID
为0L
并且其序列形式为空字符串。
@Test
public void serialized_form()
{
Verify.assertSerializedForm(
0L,
"",
new MyClass());
}
运行测试。它将失败,因为String表示Base64编码并且永远不会为空。
org.junit.ComparisonFailure: Serialization was broken. <Click to see difference>
当您单击以查看差异时,您将看到实际的Base64编码。将其粘贴在空字符串中。
@Test
public void serialized_form()
{
Verify.assertSerializedForm(
0L,
"rO0ABXNyAC9jYXJhbWVsa2F0YS5zaHVrbmlfZ29lbHZhLkV4ZXJjaXNlOVRlc3QkTXlDbGFzc56U\n"
+ "hVp0q+1aAgAAeHA=",
new MyClass());
}
重新运行测试。它可能会再次失败并显示如下错误消息。
java.lang.AssertionError: serialVersionUID's differ expected:<0> but was:<-7019839295612785318>
将新的serialVersionUID粘贴到测试中代替0L。
@Test
public void serialized_form()
{
Verify.assertSerializedForm(
-7019839295612785318L,
"rO0ABXNyAC9jYXJhbWVsa2F0YS5zaHVrbmlfZ29lbHZhLkV4ZXJjaXNlOVRlc3QkTXlDbGFzc56U\n"
+ "hVp0q+1aAgAAeHA=",
new MyClass());
}
测试现在将通过,直到您更改序列化表格。如果您意外中断测试(更改序列化表单),首先要做的是检查您是否在Serializable类中指定了serialVerionUID
。如果你把它留下来,JVM会为你生成它并且非常脆弱。
public class MyClass implements Serializable
{
private static final long serialVersionUID = -7019839295612785318L;
}
如果测试仍然中断,您可以尝试通过将新字段标记为瞬态来恢复序列化表单,使用writeObject()等完全控制序列化表单。
如果测试仍然中断,您必须决定是否查找并还原您的更改,这些更改会导致序列化或将更改视为对序列化表单的有意更改。
当您故意更改序列化表单时,您需要更新Base64字符串以使测试通过。当你这样做时,至关重要你同时改变了serialVersionUID
。你选择的数字并不重要,只要它是你以前从未用过的数字。惯例是将其更改为2L
,然后更改为3L
等。如果您是从随机生成的serialVersionUID
(例如示例中的-7019839295612785318L
)开始的,那么您应该仍然将数字提升到2L
,因为它仍然是序列化表单的第二个版本。
注意:我是GS系列的开发人员。
答案 1 :(得分:2)
当您需要长时间保存数据时,最好不要使用序列化。尝试使用数据库或protocol buffer(协议缓冲区是一种以高效且可扩展的格式编码结构化数据的方法)。
答案 2 :(得分:2)
Java的本机序列化支持主要用于短期存储或通过网络传输,因此应用程序的实例可以轻松通信。如果您需要长期存储,我建议您查看一些XML序列化技术,如JAXB。
答案 3 :(得分:2)
如果要管理类的序列化版本,则应实现接口Externalizable并指定如何序列化和反序列化类的状态。这样,序列化状态可以比“真实”状态更简单。例如,TreeMap对象的状态为红黑树,而序列化版本只是键值列表(并且在反序列化对象时重新创建树)。
但是,如果您的类很简单且只有一些可选字段,则可以使用关键字“transient”并使默认序列化忽略它。例如:
public class Foo {
private String bar;
private transient Integer eggs;
}
答案 4 :(得分:1)
不幸的是,我对C#没有深入的了解,但根据你的话,我可以得出Java序列化较弱的结论。字段serialVersionUID是可选的,只有在您更改了类二进制签名但未更改可序列化字段时才有用。如果您更改了字段,则无法读取先前序列化的对象。
唯一的解决方法是实施自己的searilzation机制。 Java允许这样做。您必须实施自己的readObject()
和writeObject()
方法。这些方法应足够智能,以支持向后兼容性。
有关详细信息,请参阅java.io.Serializable
的javadoc。
答案 5 :(得分:0)
如果将serialVersionUID设置为常量(假设为1),则可以自由添加新字段而不会破坏任何内容。通过在版本之间保持serialVersionUID相同,您告诉序列化算法您知道这些类是兼容的。