user2430771
user2430771

Reputation: 1422

Deserializing old object after new member variable is added: Java

I have a class called Employee and following is the structure:

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);
  }

}

I wrote WriteEmployee.java which writes Employee object into file 'employee.ser' as follows:

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());
            }
        }
    }

  }
}

In the Employee.java, I decided to add 'gender' field, so I added this code to Employee.java

private String gender;

public String getGender() {
    return gender;
}

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

And I also modified my readObject() and writeObject() in Employee.java as follows:

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);
}

I'm trying to read my 'employee.ser' file which contains data only about the name and age but not about gender. However, I get 'null' in return. Here's my ReadEmployee.java code:

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());
    }
  }
}

I followed suggestion which I had found on this forum and other online tutorials was to

  1. Add a serialVersionUID
  2. Write a custom readObject() and writeObject() method

I cannot find any tutorial where they can show an example of writing an old object, adding a new field and reading the old object appropriately. Can anyone guide me with an example code what I need to make change to so that I can read the old object after modifying my class by adding a new member variable, keeping my UID unchanged.

Upvotes: 0

Views: 266

Answers (1)

VGR
VGR

Reputation: 44328

Your new writeObject method is writing data that is incompatible with the previously serialized form. There are correct ways to “evolve” a serializable class, but completely replacing its serialized form is not one of them.

As long as your writeObject method does its own writing of fields, there is no way your readObject method can detect which serialized form (old vs. new) is in an ObjectInputStream.

You don’t need to override readObject and writeObject at all. Just omit them entirely. Deserializing an older version which did not write a gender field will simply result in Employee objects with a null gender.

If you insist on setting gender to a non-null value, you can override readObject (but do not override writeObject). It is imperative that you call the defaultReadObject method before doing anything else; that will read all the old fields’ values. Then you can decide what to do about a null gender:

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

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

You should have included a serialVersionUID with your original version of Employee:

private static final long serialVersionUID = 1;

But you didn’t, which means Java computed one from the hashes of Employee’s fields. Once you add the gender field, that will create a new and incompatible default serialVersonUID, due to having one more field to hash, which will prevent deserialization.

You can address this by telling Java to treat the new version of Employee as being the same as the old version, for deserialization matching purposes, by computing the old version’s default serialVersionUID and having the new version explicitly use it:

  • Comment out the gender field and its accessor methods.
  • Use the serialver tool, which comes with every JDK, to determine the default serialVersionUID that was automatically computed for the Employee class.
  • Restore the gender field and associated accessor methods.
  • Copy the new serialVersionUID into the class: private static final long serialVersionUID = 123456789abcdefL; (Replace 123456789abcdefL with the output of the serialver command.)

Upvotes: 1

Related Questions