java 字段添加或删除等类更改是否保持了 Serializable 的向后兼容性?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6374646/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 15:39:14  来源:igfitidea点击:

Do class changes like field addition or removal maintain Serializable's backward compatibility?

javaserialization

提问by usr-local-ΕΨΗΕΛΩΝ

I have a question about Java serialization in scenarios where you may need to modify your serializable class and maintain backward compatibility.

在您可能需要修改可序列化类并保持向后兼容性的情况下,我有一个关于 Java 序列化的问题。

I come from deep C# experience, so please allow me to compare Java with .NET.

我拥有深厚的 C# 经验,所以请允许我将 Java 与 .NET 进行比较。

In my Java scenario, I need to serialize an object with Java's runtime serialization mechanism, and store the binary data in permanent storage to reuse the objects in future. The problem isthat, in the future, classes may be subject to changes. Fields may be added or removed.

在我的 Java 场景中,我需要使用 Java 的运行时序列化机制来序列化一个对象,并将二进制数据存储在永久存储中,以便将来重用这些对象。问题是,将来,类可能会发生变化。可以添加或删除字段。

I don't know Java serialization in the deep, except for this fantastic article about how not to program in Javawhen dealing with serialization. As I imagine(d), the serialVersionUID plays a key role in Java serialization, and this is where I need your help.

我对 Java 序列化一无所知,除了这篇关于在处理序列化时如何不使用 Java 编程的精彩文章。正如我想象的那样(d),serialVersionUID 在 Java 序列化中起着关键作用,这就是我需要您帮助的地方。

Apart from the article's example (I know it's bad coding), shall that field not be modified when Eclipse asks to update it after I modified the class?

除了文章的例子(我知道这是错误的编码),当我修改类后 Eclipse 要求更新它时,该字段不应该被修改吗?

I remember from the .NET world that when I add new fields I must add the [OptionalField]Attribute to the field to get the backward compatibility, so CLR won't require it in old serialized data. Also, when I need to deprecate a field I must only remove the public methods and not the private fields.

我记得在 .NET 世界中,当我添加新字段时,我必须将[OptionalField]Attribute添加到该字段以获得向后兼容性,因此 CLR 不会在旧的序列化数据中要求它。此外,当我需要弃用一个字段时,我必须只删除公共方法而不是私有字段。

What are the guidelines for best serialization?

最佳序列化的准则是什么?

Thank you.

谢谢你。

[Add] Here is an example. Suppose I have class Foo

[添加] 这里是一个例子。假设我有 Foo 类

public class Foo {
    private String bar;
}

Then I change to:

然后我改为:

public class Foo {
    private String bar;
    private Integer eggs;
}

Is compatibility broken between these two version? If I deserialize an "oldFoo" when I have the "newFoo" compiled, does eggs equals null or is an exception thrown? I prefer the first, obviously!!

这两个版本之间的兼容性是否已损坏?如果在编译“newFoo”时反序列化“oldFoo”,eggs 是否等于 null 或抛出异常?我更喜欢第一个,很明显!!

采纳答案by Craig P. Motlin

Let's say you have a class MyClassand you want to ensure serialization compatibility going forward, or at least make sure that you don't change its serialized form unintentionally. You can use Verify.assertSerializedForm()from GS Collections test utilitiesin most cases.

假设您有一个类,MyClass并且您希望确保序列化的兼容性,或者至少确保您不会无意中更改其序列化形式。在大多数情况下,您可以使用Verify.assertSerializedForm()来自GS Collections 的测试实用程序

Start by writing a test that asserts that your class has a serialVersionUIDof 0Land has a serial form that's the empty string.

首先编写一个测试,该测试断言您的类具有一个serialVersionUIDof0L并且具有一个空字符串的串行形式。

@Test
public void serialized_form()
{
  Verify.assertSerializedForm(
    0L,
    "",
    new MyClass());
}

Run the test. It will fail since the String represents a Base64 encoding and is never empty.

运行测试。它会失败,因为 String 代表 Base64 编码并且永远不会为空。

org.junit.ComparisonFailure: Serialization was broken. <Click to see difference>

When you click to see the difference, you'll see the actual Base64 encoding. Paste it inside the empty string.

当您单击以查看差异时,您将看到实际的 Base64 编码。将其粘贴到空字符串中。

@Test
public void serialized_form()
{
  Verify.assertSerializedForm(
    0L,
    "rO0ABXNyAC9jYXJhbWVsa2F0YS5zaHVrbmlfZ29lbHZhLkV4ZXJjaXNlOVRlc3QkTXlDbGFzc56U\n"
      + "hVp0q+1aAgAAeHA=",
    new MyClass());
}

Re-run the test. It's likely to fail again with an error message like this.

重新运行测试。它很可能会再次失败并显示这样的错误消息。

java.lang.AssertionError: serialVersionUID's differ expected:<0> but was:<-7019839295612785318>

Paste the new serialVersionUID into the test in place of 0L.

将新的 serialVersionUID 粘贴到测试中以代替 0L。

@Test
public void serialized_form()
{
  Verify.assertSerializedForm(
    -7019839295612785318L,
    "rO0ABXNyAC9jYXJhbWVsa2F0YS5zaHVrbmlfZ29lbHZhLkV4ZXJjaXNlOVRlc3QkTXlDbGFzc56U\n"
      + "hVp0q+1aAgAAeHA=",
    new MyClass());
}

The test will now pass until you change the serialized form. If you break the test (change the serialized form) by accident, the first thing to do is check that you've specified the serialVerionUIDin the Serializable class. If you leave it out, the JVM generates it for you and it's quite brittle.

测试现在将通过,直到您更改序列化形式。如果您不小心破坏了测试(更改了序列化形式),首先要做的是检查您是否serialVerionUID在 Serializable 类中指定了。如果你忽略它,JVM 会为你生成它,而且它非常脆弱。

public class MyClass implements Serializable
{
  private static final long serialVersionUID = -7019839295612785318L;
}

If the test is still broken, you can try to restore the serialized form by marking new fields as transient, taking full control over the serialized form using writeObject(), etc.

如果测试仍然失败,您可以尝试通过将新字段标记为瞬态,使用 writeObject() 等完全控制序列化表单来恢复序列化表单。

If the test is still broken, you have to decide whether to find and revert your changes which broke serialization or treat your changes as an intentional change to the serialized form.

如果测试仍然失败,您必须决定是查找并恢复破坏序列化的更改,还是将您的更改视为对序列化表单的有意更改。

When you change the serialized form on purpose, you'll need to update the Base64 String to get the test to pass. When you do, it's crucialthat you change the serialVersionUIDat the same time. It doesn't matter what number you choose, as long as it's a number you've never used for the class before. The convention is to change it to 2L, then 3L, etc. If you're starting from a randomly generated serialVersionUID(like -7019839295612785318Lin the example), you should still bump the number to 2Lbecause it's still the 2nd version of the serialized form.

当您有意更改序列化形式时,您需要更新 Base64 字符串以使测试通过。当你这样做时,同时改变它是至关重要的serialVersionUID。选择哪个号码并不重要,只要它是您以前从未在课堂上使用过的号码即可。约定是将其更改为2L,然后3L等。如果您从随机生成的开始serialVersionUID(如-7019839295612785318L示例中所示),您仍然应该将数字增加到 ,2L因为它仍然是序列化形式的第二个版本。

Note: I am a developer on GS collections.

注意:我是 GS 系列的开发人员。

回答by Matteo

If you want to manage the serialized version of the class, you should implement interface Externalizableand specify how to serialize and deserialize the state of your class. This way, the serialized state can be simpler than the "real" state. For example, a TreeMap object has a state that is a red-black tree, while the serialized version is just a list of key-values (and the tree is re-created when the object is deserialized).

如果你想管理类的序列化版本,你应该实现接口Externalizable并指定如何序列化和反序列化你的类的状态。这样,序列化状态可以比“真实”状态更简单。例如,一个 TreeMap 对象的状态是一棵红黑树,而序列化版本只是一个键值列表(并且在对象反序列化时重新创建树)。

However, if your class is simple and it only has some optional fields, you can use the keyword "transient" and make the default serialization ignore it. For example:

但是,如果您的类很简单并且只有一些可选字段,则可以使用关键字“transient”并使默认序列化忽略它。例如:

public class Foo {
    private String bar;
    private transient Integer eggs;
}

回答by Emil

It's best not to use serialization when you need to keep your data for long period of time.Try using a database or protocol buffer(Protocol Buffers are a way of encoding structured data in an efficient yet extensible format).

当您需要长时间保留数据时,最好不要使用序列化。尝试使用数据库或协议缓冲区(协议缓冲区是一种以高效但可扩展的格式对结构化数据进行编码的方法)。

回答by Waldheinz

Java's native serialization support is mainly useful for short term storage or transmission via a network, so instances of an application can communicate with little effort. If you're after longer term storage, I'd suggest you have a look at some XML serialization technique like JAXB.

Java 的本机序列化支持主要用于短期存储或通过网络传输,因此应用程序的实例可以毫不费力地进行通信。如果您追求长期存储,我建议您查看一些 XML 序列化技术,例如 JAXB。

回答by AlexR

Unfortunately I do not have a deep knowledge of C# but based on your words I can conclude that Java serialization is weaker. Field serialVersionUID is optional and can help only if you changed the class binary signature but have not changed the serializable fields. If you changed the fields you cannot read previously serialized object.

不幸的是,我对 C# 没有深入的了解,但根据您的话,我可以得出结论,Java 序列化较弱。字段 serialVersionUID 是可选的,只有在您更改了类二进制签名但未更改可序列化字段时才有帮助。如果更改了字段,则无法读取先前序列化的对象。

The only workaround is to implement your own searilzation mechanism. Java allows this. You have to implement your own readObject()and writeObject()methods. These methods should be smart enough to support backwards compatibility.

唯一的解决方法是实现您自己的 serilization 机制。Java 允许这样做。你必须实现自己readObject()writeObject()方法。这些方法应该足够智能以支持向后兼容。

Please see javadoc of java.io.Serializablefor more details.

java.io.Serializable有关更多详细信息,请参阅的 javadoc 。

回答by James Scriven

If you set the serialVersionUID to a constant (let's say 1) then you can freely add new fields without breaking anything. By leaving the serialVersionUID the same between versions, you are telling the serialization algorithm that youknow that the classes are compatible.

如果您将 serialVersionUID 设置为常量(假设为 1),那么您可以自由添加新字段而不会破坏任何内容。通过在版本之间保持 serialVersionUID 相同,您告诉序列化算法知道这些类是兼容的。