代码之家  ›  专栏  ›  技术社区  ›  KarlP

管理序列化Java对象的多个版本

  •  33
  • KarlP  · 技术社区  · 15 年前

    例如:反序列化时,可能会遇到这些版本之一。

    class Pet {
        private static final long serialVersionUID = 1L;
        int paws;
    }
    
    class Pet {
        private static final long serialVersionUID = 2L;
        long paws; // handle marsian centipedes
        boolean sharpTeeth;
    }
    

    让我们假设(逻辑上)可以使用一些聪明的策略来设置不存在的字段等,将旧对象转换为新对象,但是:

    如何安排源代码?在编写转换器时,我可能需要将两个版本放在同一个源代码树中,但是在eclipse中如何处理呢。

    我应该在一个类加载器中进行反序列化,如果失败,请尝试使用另一个使用旧版本(以此类推)的类加载器,或者有更好的方法吗?

    4 回复  |  直到 11 年前
        1
  •  34
  •   Gray droiddeveloper    8 年前

    让我们假设(逻辑上)可以使用一些巧妙的策略来设置不存在的字段等,将旧对象转换为新对象。。。如何安排源代码?

    serialVersionUID 除非你愿意 InvalidClassException 被扔掉。第二条规则是 更改 类型 boolean sharpTeeth; 但是该类没有该字段,那么在反序列化过程中将忽略该字段。如果反序列化类具有 sharpTeeth 字段,但文件不存在,则会将其初始化为默认值, false 在这种情况下。

    序列版本ID 但只要添加或删除字段,就可以做到这一点。实体的较新版本需要支持较旧版本,但较旧的实体不会介意新字段是否可用。这也意味着您不应该更改字段的比例。

    paws 从一个 int long . 相反,我建议添加一个 long pawsLong 或者写一些这样的代码来处理存在的可能性 int paws 长爪

    public long getPaws() {
        if (pawsLong > 0) {
            return pawsLong;
        } else {
            // paws used to be an integer
            return paws;
        }
    }
    

    你也可以自己写 readObject

    private void readObject(java.io.ObjectInputStream in) {
        super.readObject(in);
        // paws used to be an integer
        if (pawsLong == 0 && paws != 0) {
            pawsLong = paws;
        }
    }
    

    如果这对您不起作用,那么定制序列化就是一种方法。但是,您必须从一开始就这样做,并定义自定义 readObject(...) writeObject(...) 具有内部版本id的方法。类似于:

    // never change this
    private static final long serialVersionUID = 3375159358757648792L;
    // only goes up
    private static final int INTERNAL_VERSION_ID = 2;
    ...
    // NOTE: in version #1, this was an int
    private long paws;
    
    private void readObject(java.io.ObjectInputStream in) {
        int version = in.readInt();
        switch (version) {
            case 1 :
                paws = in.readInt();
                ...
            case 2 :
                paws = in.readLong();
                ...
    
    private void writeObject(java.io.ObjectOutputStream out) {
        out.writeInt(INTERNAL_VERSION_ID);
        out.writeLong(paws);
        ...
    

    但是这种方法不能帮助您提高兼容性。版本1读取器无法理解版本2序列化输入。

    我应该在一个类加载器中进行反序列化,如果失败,请尝试使用另一个使用旧版本(以此类推)的类加载器,或者有更好的方法吗?

    我不建议任何这些方法。听起来很难维持。

        2
  •  11
  •   Bozho    15 年前

    很遗憾,不允许更改字段类型。支持两个(十,一百?)不同的版本太费劲了。所以你可以利用 readObject(ObjectInputStream in) 方法。设定一个固定的 serialVersionUID . 如果您最初没有设置它,请使用IDE或JDK serialver 为了得到它,所以看起来你只有一个类的版本。

    如果要更改字段的类型,请同时更改其名称。例如 paws &燃气轮机; pawsCount readObject(..) 方法,如果字段中存在类型不匹配。

    对于上述例子,一个可行的解决方案是:

    class Pet implements Serializable {
        private static final long serialVersionUID = 1L;
        long pawsCount; // handle marsian centipedes
        boolean sharpTeeth;
    
        private void readObject(java.io.ObjectInputStream in)
            throws IOException, ClassNotFoundException {
    
            in.defaultReadObject();
            GetField fields = in.readFields();
            int paws = fields.get("paws", 0); // the 0 is a default value 
            this.pawsCount = paws;
        }
    }
    

    以后添加的字段将设置为其默认值。

    顺便说一句,它可能更容易使用 java.beans.XMLEncoder (如果对你的项目来说还不算太晚)

        3
  •  2
  •   Juliet    15 年前

    我应该在一个 旧版本(等等),或者 有更好的方法吗?

    最好的策略是什么?

    这里最好的策略是使用数据库:将对象存储在 Pets 表,然后当您更改表上的字段时,所有旧数据也会更新,每个对象都有相同且最新的模式。

    这确实是为长期存储维护数据的最佳方法,而且更新旧对象以填充空字段非常容易。

        4
  •  -2
  •   Gabriel Devillers    4 年前

    您不必维护类的多个版本。最新版本就足够了。查看链接 5 things you don't know about Serialization 特别是“重构序列化类”

    推荐文章