Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serializable subclass of non-serializable parent class

I am hitting a brickwall with serialization of a subclass of Location in android/java

Location is not serializable. I have a first subclass called FALocation that does not have any instance variables. I have declared it serializable.

Then I have a second class called Waypoint that looks like this:

public class Waypoint extends FALocation implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /* Class variables *******************************************************/
    private static int CLASS_VERSION=1; //Used to version parcels

    /* Instance variables ****************************************************/
    private transient String type=DataHelper.PT_TYPE_US;
    private transient String country; 
    private transient String name=null;
    private transient String description=null;
    private transient int elevation = 0;
    private transient int population = 0; // Afterthought, added to match the DB structure

    /* Constructors **********************************************************/    
    public Waypoint() {
        super();
    }

    public Waypoint(double lat, double lon, String name, String description) {
        super(lat, lon);
        this.setName(name);
        this.setDescription(description);
    }

    public Waypoint(Location l) {
        super(l);
    }

    public Waypoint(String provider) {
        super(provider);
    }


    /* Implementing serializable */
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        Log.v("DroidFA", "Serialising \"%s\" (v%d).", Waypoint.class.getSimpleName(), CLASS_VERSION);
        out.writeInt(CLASS_VERSION);

        out.writeObject(type);
        out.writeObject(country);
        out.writeObject(name);
        out.writeObject(description);
        out.writeInt(elevation);
        out.writeInt(population);
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {

        int serialClassVersion = in.readInt();
        Log.v("DroidFA", "Deserialising \"%s\" (v%d).", Waypoint.class.getSimpleName(),serialClassVersion);

        type = (String) in.readObject();
        country = (String) in.readObject();
        name = (String) in.readObject();
        description = (String) in.readObject();
        elevation = in.readInt();
        population = in.readInt();
    }
}

Serialization works fine.

Deseriamization produces the followwing exception (the leg object contains a waypoint).:

10-05 13:50:35.259: WARN/System.err(7867): java.io.InvalidClassException: android.location.Location; IllegalAccessException
10-05 13:50:35.267: WARN/System.err(7867):     at java.io.ObjectInputStream.resolveConstructorClass(ObjectInputStream.java:2010)
10-05 13:50:35.267: WARN/System.err(7867):     at java.io.ObjectInputStream.readNewObject(ObjectInputStream.java:2095)
10-05 13:50:35.267: WARN/System.err(7867):     at java.io.ObjectInputStream.readNonPrimitiveContent(ObjectInputStream.java:929)
10-05 13:50:35.267: WARN/System.err(7867):     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:2285)
10-05 13:50:35.278: WARN/System.err(7867):     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:2240)
10-05 13:50:35.278: WARN/System.err(7867):     at com.droidfa.navigation.Leg.readObject(Leg.java:262)
.../...
like image 221
jmc34 Avatar asked Oct 05 '11 14:10

jmc34


2 Answers

Is it absolutely necessary to serialize the Location? maybe you could mark it as transient, and obtain it dynamically after deserializing the object. (Anyway, from the documentation ) :

Q: If class A does not implement Serializable but a subclass B implements Serializable, will the fields of class A be serialized when B is serialized?

A: Only the fields of Serializable objects are written out and restored. The object may be restored only if it has a no-arg constructor that will initialize the fields of non-serializable supertypes. If the subclass has access to the state of the superclass it can implement writeObject and readObject to save and restore that state.

So, if the subclass has access to the fields of its non-serializable superclass(es) it can use the writeObject and readObject protocol to implement serialization. Otherwise, there will be fields that won't be possible to serialize.

like image 161
Óscar López Avatar answered Nov 15 '22 18:11

Óscar López


Looks like Location does not have public/protected no-arg constructor. Such a constructor is needed for making it available for serialization in subclass.

http://download.oracle.com/javase/6/docs/api/java/io/Serializable.html says:

To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime.

And same with the words from Serialization specification:

A Serializable class must do the following: ... Have access to the no-arg constructor of its first nonserializable superclass

That would explain why you have problems only in deserialization, because naturally constructor is not called during serialization.

Small example of failing without accessible constructor:

public class A {
    public A(String some) {};
    private A() {} //as protected or public everything would work
}

public class B extends A implements Serializable {
    public B() {
        super("");
    }
    //these doesn't really matter
    //private void writeObject(java.io.ObjectOutputStream out) throws IOException {  }
    //private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { }
}

public class BSerializer {

    public static void main(String ... args) throws Exception {
        B b = new B();

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(b);
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        B deserialized = (B) ois.readObject();   //InvalidClassException
    }
}
like image 31
Mikko Maunu Avatar answered Nov 15 '22 17:11

Mikko Maunu