首页 > 解决方案 > 添加新成员变量后反序列化旧对象:Java

问题描述

我有一个名为 Employee 的类,结构如下:

package Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Employee implements Serializable {

  private static final long serialVersionUID = -299482035708790407L;

  private String name;
  private int age;

  public String getName() {
      return name;
  }

  public int getAge() {
      return age;
  }

  void setName(String name) {
      this.name = name;
  }

  public void setAge(int age) {
      this.age = age;
  }


   @Override
  public String toString() {
      return "Name: " + name + ", " + "age: " + age ;
  }


  private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
    setName(ois.readUTF());
    setAge(ois.readInt());


  }

  private void writeObject(ObjectOutputStream out) throws IOException {
      out.writeUTF(name);
      out.writeInt(age);
  }

}

我编写了 WriteEmployee.java,它将 Employee 对象写入文件'employee.ser',如下所示:

public class WriteEmployee {

public static void main(String[] args) {
    FileOutputStream fos = null;
    ObjectOutputStream os = null;
    try {
        fos = new FileOutputStream("employee.ser");
        os = new ObjectOutputStream(fos);
        Employee e = new Employee();
        e.setName("User123");
        e.setAge(28);

        ((ObjectOutputStream) os).writeObject(e);
    } catch (IOException e) {
        System.out.println(e.getMessage());
    } finally {
        if (os !=null ) {
            try {
                os.close();

            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }

  }
}

在 Employee.java 中,我决定添加“性别”字段,所以我将此代码添加到 Employee.java

private String gender;

public String getGender() {
    return gender;
}

public void setGender(String gender) {
    this.gender = gender;
}

我还修改了 Employee.java 中的 readObject() 和 writeObject() 如下:

private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
    setName(ois.readUTF());
    setAge(ois.readInt());
    setGender(ois.readUTF());


}

private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeUTF(name);
    out.writeInt(age);
    out.writeUTF(gender);
}

我正在尝试阅读我的“employee.ser”文件,该文件仅包含有关姓名和年龄的数据,但不包含有关性别的数据。但是,我得到'null'作为回报​​。这是我的 ReadEmployee.java 代码:

public class ReadEmployee {

public static void main(String[] args) {

    FileInputStream fis = null;
    ObjectInputStream is = null;
    try {
        fis = new FileInputStream("employee.ser");
        is = new ObjectInputStream(fis);
        Employee e = (Employee)is.readObject();
        System.out.println(e);
        fis.close();
        is.close();

    } catch (IOException | ClassNotFoundException e) {
        System.out.println(e.getMessage());
    }
  }
}

我遵循了我在这个论坛上找到的建议,其他在线教程是

  1. 添加一个serialVersionUID
  2. 编写自定义的 readObject() 和 writeObject() 方法

我找不到任何教程,他们可以展示编写旧对象、添加新字段和适当读取旧对象的示例。任何人都可以通过示例代码指导我需要更改的内容,以便我可以在通过添加新成员变量修改我的类后读取旧对象,保持我的 UID 不变。

标签: javaserializationdeserialization

解决方案


您的新 writeObject 方法正在写入与先前序列化表单不兼容的数据。有一些正确的方法可以“进化”一个可序列化的类,但完全替换它的序列化形式并不是其中之一。

只要您的 writeObject 方法自己写入字段,您的 readObject 方法就无法检测到 ObjectInputStream 中的序列化形式(旧的还是新的)。

您根本不需要覆盖 readObject 和 writeObject。完全省略它们。反序列化未写入gender字段的旧版本只会导致 Employee 对象带有 null gender

如果您坚持设置gender为非空值,则可以覆盖 readObject(但不要覆盖 writeObject)。在执行任何其他操作之前,您必须调用defaultReadObject方法;这将读取所有旧字段的值。然后,您可以决定如何处理 null 性别:

private void readObject(ObjectInputStream ois)
throws ClassNotFoundException,
       IOException {

    ois.defaultReadObject();
    if (gender == null) {
        gender = "unknown";
    }
}

您应该在 Employee 的原始版本中包含一个 serialVersionUID:

private static final long serialVersionUID = 1;

但你没有,这意味着 Java 从 Employee 字段的散列中计算了一个。添加该gender字段后,将创建一个新的且不兼容的默认 serialVersonUID,因为还有一个要散列的字段,这将阻止反序列化。

您可以通过告诉 Java 将 Employee 的新版本与旧版本相同来解决此问题,以达到反序列化匹配的目的,通过计算旧版本的默认 serialVersionUID 并让新版本显式使用它:

  • 注释掉该gender字段及其访问器方法。
  • 使用每个 JDK 附带的serialver工具来确定为 Employee 类自动计算的默认 serialVersionUID。
  • 恢复gender字段和关联的访问器方法。
  • 将新的 serialVersionUID 复制到类中:( private static final long serialVersionUID = 123456789abcdefL; 替换123456789abcdefL为 serialver 命令的输出。)

推荐阅读