I'm making compatible modifications to the my Serializable class, namely adding a few primitive fields (boolean, long). My class is a client-side representation of data provided by a server.
When I load the class from a file, I need to be able to detect if the loaded class was saved by an earlier version, i.e. it was missing some fields which java set to the default value on load. If this is the case, my app will still work, but when the client has network connectivity, I'd like to fetch the new fields from the server.
Does Java have any built-in methods to detect if a class missing fields was loaded?
(I realize I can solve this manually with a "version" instance member; I was hoping though that Java has something built-in)
You can override the readObject() method of the Serializable interface to custom handle the deserialization of your class.
Note it is strongly recommended that you also include a serialVersionUID whenever you implement Serializable:
private static final long serialVersionUID = 1;
If you don't define it, one is created from a Hash of your class's members... then if you later add things as you describe, the UID won't match and Java won't let you reload old data.
more info from the Serializable docs here: http://docs.oracle.com/javase/6/docs/api/java/io/Serializable.html
Futher information on handling non-breaking changes:
You should really only ever change the serialVersionUID if there are breaking changes between implementations which make it nonsensical to deserialize an old version with your new code.
If you modify your class with non-breaking changes by adding some fields, you shouldn't change the serialVersionUID. If the original class had no specified serialVersionUID, then one was auto-generated for you, and a new one will be autogenerated in your new implementation that doesn't match the old one.
When you try to deserialize an old version with a serialVersionUID mismatch, the system will throw java.io.InvalidClassException. The error message with this exception will tell you what the old serialVersionUID was and what the new one is.
You can then modify your class to have an explicitly specified serialVersionUID that matches the old version, allowing you to deserialize old versions and new without exceptions being thrown.
The run-time will then set any new primitive members to their default values when deserializing an old version instance.
If you want to detect whether you've deserialized an old version of the class or a new one, you can check for a default value which shouldn't be default in your readObject() implementation.
Here's an example:
// add a new private field to the modified class for tracking version:
private int myNewVersionFlag = 123;
// then in your readObject implementation:
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
stream.defaultReadObject();
if( this.myNewVersionFlag == 0 ) {
// if we get here the object being deserialized must
// be an old version, so now set sensible defaults
// for the new params or whatever...
}
}
There are other more sophisticated approaches to this including implementing your own subclass of ObjectInputStream, but in general, I find that simply having a version field that shouldn't be zero is simple and sufficient for many use-cases.
Summary
If you always specify your own serialVersionUID your code is more efficient (it doesn't have to auto-generate a hashed one) and you are better able to handle future class changes explicitly.
You should only change this value if your changes are breaking and would mean deserializing an old version of your class is impossible due to no sensible defaults being possible for new features of the class.
If you have your own private member for tracking version numbers you can use this within readObject() to insert sensible defaults to new variables when deserializing old instances if you need to.
Finally, everything you ever wanted to know about Deserialization (but were afraid to ask) is covered in the Java serialization specification:
http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html