防止 Java 序列化设置默认值

发布于 2025-01-04 18:57:41 字数 350 浏览 0 评论 0原文

我有一个包含整数 foo 的序列化对象 MyObject。我将值 10 设置为整数 foo 并使用 writeObject() 将对象保存到文件中。

我将整数条添加到对象 MyObject。我将值 15 设置为整数 bar,然后使用 readObject() 加载旧的序列化文件。

旧的可序列化文件不包含整数 bar,因此整数 bar 将获得值 0。如果旧的可序列化文件不包含变量 bar,我想在 bar 中保留值 15。

我应该覆盖 readObject() 或者如何阻止 readObject() 将“默认值”设置为未知对象?

我想这样做是因为在构造函数中我设置了自己的默认值,并且希望使用自己的默认值来控制版本控制。

I have a serialized object MyObject that contains integer foo. I set a value 10 to integer foo and save the object to a file using writeObject().

I add integer bar to object MyObject. I set a value 15 to integer bar and then load the old serialized file using readObject().

The old serializable file doesn't contain integer bar so integer bar will get value 0. I want to keep the value 15 in bar if the old serializable file doesn't contain variable bar.

Should I override readObject() or how could I prevent readObject() from setting "default values" to unknown objects?

I want to do this because in the constructor I'm setting my own default values and would like to use my own default values to control versioning.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

又怨 2025-01-11 18:57:41

序列化不设置默认值,它遵循 Java 的默认值初始化方案。

如果我能总结一下你的问题。您希望序列化将序列化流中的内容与内存中的值合并。对于 Java 序列化来说这是不可能的,因为它控制要创建的对象。您可以读取序列化对象,然后手动编写代码来合并要合并在一起的字段。如果您坚持使用 Java 序列化(如果我是您,我会避开它),但假设您想继续使用它。

public class MyObject {

    public void merge( MyObject that ) {
        // given some other instance of an object merge this with that.
        // write your code here, and you can figure out the rules for which values win.
    }
}

ObjectInputStream stream = new ObjectInputStream( new FileInputStream( file ) );
MyObject that = stream.readObject();
someObject.merge( that );

Viola 您可以控制哪些字段将从该字段合并到 someObject 中。如果您希望库为您执行此合并,请查看http://flexjson.sourceforge.net。它使用 JSON 序列化,并通过 Bean 而不是 POJO 工作。但是,有一种方法可以获取已填充的对象并覆盖 JSON 流中的值。这有其局限性。这样做的另一个好处是,在对象结构发生更改后,您实际上可以读回流,而 Java 序列化在技术上可以做到这一点,但它非常非常困难。

Serialization doesn't set default values it defers to Java's default value initialization scheme.

If I can sum up your question. You want serialization to merge what's in the serialized stream with the values in memory. That's not possible with Java serialization as it controls what objects to create. You can read in your serialized object, then manually write the code to merge what fields you want merged together. If your stuck on Java serialization (I'd steer clear of it if I were you), but let's say you want to continue using it.

public class MyObject {

    public void merge( MyObject that ) {
        // given some other instance of an object merge this with that.
        // write your code here, and you can figure out the rules for which values win.
    }
}

ObjectInputStream stream = new ObjectInputStream( new FileInputStream( file ) );
MyObject that = stream.readObject();
someObject.merge( that );

Viola you control which fields will be merged from that into someObject. If you want a library to do this merge for you check out http://flexjson.sourceforge.net. It uses JSON serialization, and works from Beans rather than POJO. However, there is a way to take an already populated object and overwrite values from a JSON stream. There are limitations to this. The other benefit of this is you can actually read the stream back AFTER your object structure has changed something that Java serialization can technically do, but its very very hard.

半衾梦 2025-01-11 18:57:41

将以下方法添加到您的 MyObject 对您有用吗?

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
{
    bar = 15;  // Set a default value of 15 if it's not in the serialized output file
    ois.defaultReadObject();
}

Would adding the following method to your MyObject work for you?

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
{
    bar = 15;  // Set a default value of 15 if it's not in the serialized output file
    ois.defaultReadObject();
}
慕巷 2025-01-11 18:57:41

这里的问题是 ObjectInputStream 不会调用默认构造函数 MyObject(),它通常也会初始化字段值。 (例如 foo=10bar=15

ObjectInputStream 生成自己的空构造函数用于反序列化,该构造函数不会初始化字段! (仅使用流中的值)

但是此问题有一个解决方法,如下所示:

  1. 创建一个不可可序列化的超类MyParent。 (MyObject 扩展 MyParent

    默认构造函数 MyParent() 将由 ObjectInputStream 调用,因为 MyParent 不是Serialized

  2. 创建一个空方法,例如 MyParent.setDefaults(),由 MyParent() 构造函数调用。

  3. MyObject.setDefaults() 子类中重写该方法,以在 ObjectInputStream 从流中读取值之前设置默认值。

    示例:

    公共类 MyParent {
        public MyParent() { // 将由 ObjectInputStream 调用!
            极好的();
            设置默认值();
        }
        public void setDefaults() { // 将由 MyParent() 调用
            // 被子类覆盖。
        }
    }
    
    公共类 MyObject 扩展 MyParent 实现可序列化 {
        私有 int foo;
        私人 int 酒吧;
        public MyObject() { // 不会被 ObjectInputStream 调用!
            极好的();
        }
        public void setDefaults() { // 在 ObjectInputStream 读取值之前将由 MyParent() 调用
            极好的();
            富 = 10;
            条= 15;
        }
    }
    

The problem here is that ObjectInputStream does not invoke the default constructor MyObject() which usually also initializes the field values. (e.g. foo=10, bar=15)

The ObjectInputStream generates its own empty constructor for deserialization, which does not initialize the fields! (Only uses the values in the stream)

But there is a workaround for this problem as follows:

  1. Create a superclass MyParent which is NOT Serializable. (MyObject extends MyParent)

    The default constructor MyParent() will then be invoked by the ObjectInputStream because MyParent is NOT Serializable.

  2. Create an empty method e.g. MyParent.setDefaults() which is called by the MyParent() constructor.

  3. Override that method in the subclass MyObject.setDefaults() to set your default values BEFORE the ObjectInputStream reads the values from the stream.

    Example:

    public class MyParent {
        public MyParent() { // will be invoked by ObjectInputStream !
            super();
            setDefaults();
        }
        public void setDefaults() { // will be invoked by MyParent()
            // to be overwritten by sub-classes.
        }
    }
    
    public class MyObject extends MyParent implements Serializable {
        private int foo;
        private int bar;
        public MyObject() { // will NOT be invoked by ObjectInputStream !
            super();
        }
        public void setDefaults() { // will be invoked by MyParent() before ObjectInputStream reads the values
            super();
            foo = 10;
            bar = 15;
        }
    }
    
隐诗 2025-01-11 18:57:41

仅使用方法“merge(obj)”的问题是,您无法区分流中已保存的旧默认值“bar=0”(情况 X >)以及没有该字段的更旧的已保存对象(情况 Y),然后与新更改的默认值(从“0”到“15”)合并。

在这种情况下,方法“merge(obj)”可能会为之前已经存在的字段使用新的默认值“15”,但在(情况 X)中,它之前已被用户保存/更改为值“0”。

情况 X:它应该从流中保存的对象恢复“0”,并且不应该使用新的默认值” 15 英寸

情况 Y:对于更旧的已保存对象(流中没有字段),它应该使用新的默认值“15”

您可以(当然)只引入一个新的“版本”字段,然后可以通过合并方法处理该字段,但这需要更多代码。

情况 Z :如果我们引用其他对象,就会出现另一个问题。
然后调用者和合并方法还需要知道调用所有引用的合并方法(递归)。

基本上,这正是 ObjectOutputStream 和 ObjectInputStream 已经使用 readObject(ois) 等方法所做的事情。

请参阅我使用 setDefaults() 的解决方法,它将在 ObjectInputStream 恢复之前调用来自流的值,和/或使用带有 readObject(ois) 的解决方案,这也可以避免该问题。

The problem with only using a method "merge(obj)" is that you cannot distinguish between an already saved old default value of "bar=0" in the stream (Case X) and an even older saved object without that field (Case Y), and then merging with a newly changed default value from "0" to "15".

In that case the method "merge(obj)" would maybe use the new default value "15" for the field that already existed before, but in (Case X) it was already saved/changed earlier with value "0" by the user.

Case X: It should restore "0" from the saved object in the stream, and SHOULD NOT use the new default value "15".

Case Y: For the even older saved object (without the field in the stream), it SHOULD use the new default value "15".

You could (of course) just introduce a new "version"-field, which then can be handled by the merge-method, but that requires much more code.

Case Z: Another problem happens if we have references to other objects.
Then the invoker and merge-methods would need to be aware of invoking the merge-methods of all references as well (recursive).

Basically, that's exactly what ObjectOutputStream and ObjectInputStream already do using the methods readObject(ois), etc.

See my workaround with setDefaults(), which will be invoked before the ObjectInputStream restores the values from the stream, and/or use the solution with readObject(ois), which also avoids that problem.

挽梦忆笙歌 2025-01-11 18:57:41

使用关键字transient从序列化/反序列化中排除字段。

Use the keyword transient to exclude fields from serialization/deserialization.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文