Serialization of enum fields in Java - java

From the Javadoc of ObjectInputStream:
Enum constants are deserialized differently than ordinary serializable
or externalizable objects. The serialized form of an enum constant
consists solely of its name; field values of the constant are not
transmitted. To deserialize an enum constant, ObjectInputStream reads
the constant name from the stream; the deserialized constant is then
obtained by calling the static method Enum.valueOf(Class, String) with
the enum constant's base type and the received constant name as
arguments. Like other serializable or externalizable objects, enum
constants can function as the targets of back references appearing
subsequently in the serialization stream. The process by which enum
constants are deserialized cannot be customized: any class-specific
readObject, readObjectNoData, and readResolve methods defined by enum
types are ignored during deserialization. Similarly, any
serialPersistentFields or serialVersionUID field declarations are also
ignored--all enum types have a fixed serialVersionUID of 0L.
Why are enums in Java not serialized on their entirety? Enums in Java are more than mere constants and are full fledged classes that can also contain state. Does it not result in inconsistent state between the sending and the receiving ends? What is that fundamental point that I am missing here?

The lesson is to not use enums when you need mutable objects. Yes, you can design enums that maintain an internal state, but they weren't designed for that. As in the case of serialization, not all parts of Java will cooperate if you do.
If you must tie enum values to state data, use an EnumMap. That class implements Serializable, so you won't need to do any additional work to serialize your state data (provided the state data objects are themselves serializable).

Related

What makes an Object serializable

I have an object with a HashMap field and a few methods that I am trying to serialize. However, at runtime, I am getting a java.io.NotSerializableException.
I was checking to see if HashMaps could be serialized and from what I have read they are so I am not sure what the problem is.
I was just wondering what makes an object be able to be serialized and why would this object that seems to only have fields that can be serialized not be able to as well.
This is defined in the Java platform Spec here:
https://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html
The basic rules are these:
"A Serializable class must do the following:
Implement the java.io.Serializable interface
Identify the fields that should be serializable (Use the
serialPersistentFields member to explicitly declare them serializable
or use the transient keyword to denote nonserializable
Have access to the no-arg constructor of its first nonserializable
superclass"
Broadly, in the absence of any indication to the contrary, and field that is not explicitly marked "transient" is a candidate for serialization.
The entire object graph from the target object downwards has to be serializable, or nothing is. That is, every field that references an object (not a primitive) must reference a serializable object.

SerializationVersionId same but class is modified?

I serialize an object and transfer it over the network.My serialized class object has serilizableId which i defined myself.Now in another JVM I keep the SeriliazibleId same but change some attributes.
What will happen and why?Will It be able deserialize it?
You must refer to the Java Object Serialization Specification here.
In the specific, what you are NOT allowed to do:
Deleting fields - If a field is deleted in a class, the stream written will not contain its value. When the stream is read by an earlier class, the value of the field will be set to the default value because no value is available in the stream. However, this default value may adversely impair the ability of the earlier version to fulfill its contract.
Moving classes up or down the hierarchy - This cannot be allowed since the data in the stream appears in the wrong sequence.
Changing a nonstatic field to static or a nontransient field to transient - When relying on default serialization, this change is equivalent to deleting a field from the class. This version of the class will not write that data to the stream, so it will not be available to be read by earlier versions of the class. As when deleting a field, the field of the earlier version will be initialized to the default value, which can cause the class to fail in unexpected ways.
Changing the declared type of a primitive field - Each version of the class writes the data with its declared type. Earlier versions of the class attempting to read the field will fail because the type of the data in the stream does not match the type of the field.
Changing the writeObject or readObject method so that it no longer writes or reads the default field data or changing it so that it attempts to write it or read it when the previous version did not. The default field data must consistently either appear or not appear in the stream.
Changing a class from Serializable to Externalizable or vice versa is an incompatible change since the stream will contain data that is incompatible with the implementation of the available class.
Changing a class from a non-enum type to an enum type or vice versa since the stream will contain data that is incompatible with the implementation of the available class.
Removing either Serializable or Externalizable is an incompatible change since when written it will no longer supply the fields needed by older versions of the class.
Adding the writeReplace or readResolve method to a class is incompatible if the behavior would produce an object that is incompatible with any older version of the class.
What you are allowed to do instead:
Adding fields - When the class being reconstituted has a field that does not occur in the stream, that field in the object will be initialized to the default value for its type. If class-specific initialization is needed, the class may provide a readObject method that can initialize the field to nondefault values.
Adding classes - The stream will contain the type hierarchy of each object in the stream. Comparing this hierarchy in the stream with the current class can detect additional classes. Since there is no information in the stream from which to initialize the object, the class's fields will be initialized to the default values.
Removing classes - Comparing the class hierarchy in the stream with that of the current class can detect that a class has been deleted. In this case, the fields and objects corresponding to that class are read from the stream. Primitive fields are discarded, but the objects referenced by the deleted class are created, since they may be referred to later in the stream. They will be garbage-collected when the stream is garbage-collected or reset.
Adding writeObject/readObject methods - If the version reading the stream has these methods then readObject is expected, as usual, to read the required data written to the stream by the default serialization. It should call defaultReadObject first before reading any optional data. The writeObject method is expected as usual to call defaultWriteObject to write the required data and then may write optional data.
Removing writeObject/readObject methods - If the class reading the stream does not have these methods, the required data will be read by default serialization, and the optional data will be discarded.
Adding java.io.Serializable - This is equivalent to adding types. There will be no values in the stream for this class so its fields will be initialized to default values. The support for subclassing nonserializable classes requires that the class's supertype have a no-arg constructor and the class itself will be initialized to default values. If the no-arg constructor is not available, the InvalidClassException is thrown.
Changing the access to a field - The access modifiers public, package, protected, and private have no effect on the ability of serialization to assign values to the fields.
Changing a field from static to nonstatic or transient to nontransient - When relying on default serialization to compute the serializable fields, this change is equivalent to adding a field to the class. The new field will be written to the stream but earlier classes will ignore the value since serialization will not assign values to static or transient fields.
If you are using private static final long serialVersionUID; for your class, then you are making sure that any version changes, until they are backward compatible, would not affect the deserialization of your class. If its not backward compatible, then you need to increment the serial version ID.

Java: Java5 enum custom serialization

For serializing large enum instances, I was thinking of using a "serialization proxy" where the proxy object would be a very small object uniquely identifying the corresponding enum instance. However I found out that any read/writeObject or resolve/replace methods are ignored for enums. So my question is, how would I efficiently serialize large enum instances? A String/XML representation is a possibility, but my feeling is this would be a maintenance issue.
You're reinventing what is already done by default by the Java serialization:
Quote from
http://docs.oracle.com/javase/1.5.0/docs/guide/serialization/relnotes15.html:
Support has been added to serialization to handle enumerated types,
which are new in version 5.0. The rules for serializing an enum
instance differ from those for serializing an "ordinary" serializable
object: the serialized form of an enum instance consists only of its
enum constant name, along with information identifying its base enum
type. Deserialization behavior differs as well--the class information
is used to find the appropriate enum class, and the Enum.valueOf
method is called with that class and the received constant name in
order to obtain the enum constant to return.

jms and different versions of enums

My team provides a service to another team in the company via jms. This involves an enum which is passed in to us as part of a larger object. They will soon be changing the definition of the enum - however, I would like to change it first so i can have our service running in production a few days before the change. The change will just involve adding another constant at the end.
Can I do this safely?
From Java Object Serialization Specification version 6.0 (emphasises are mine):
The serialized form of an enum constant consists solely of its name; [...]. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments.
This means that:
Adding new value to the enum is backward compatible when it comes to serialization
Reordering existing enum values is backward compatible (in contrary to default JPA enum persistence strategy that uses ordinal())
Removing values from enum is not backward compatible in general, since the removed values might have already been used for serialization purposes
Renaming enum values is not backward compatible (see above)
If I understand right, your object containing the enum is sent as a part of an ObjectMessage. If this is the case, it used usual Java Serialization to encode and decode the enum object.
Serialization for enums works by name - i.e. in fact they are sending the name of the enum value (together with an identifier for its class). As long as the values actually sent are existent on the other receiving side, there should be no problem.
Of course, you have to make sure the meaning of the message doesn't change by your change.

When should I change a SerialUID?

I have a bunch of serialized classes. Normally I have generated serial UIDs for all of them as the Java rules are rather restrictive and recreate Serial Numbers with basically any change. But this lead me to the question, that I haven't been able to find an answer for in the internet:
When does it make sense to break backwards compatibility and manually change the Serial Version UID in the class?
Section 5.6 of the Java Spec helps here:
http://download.oracle.com/javase/6/docs/platform/serialization/spec/version.html#6678
5.6 Type Changes Affecting Serialization
With these concepts, we can now describe how the design will cope with
the different cases of an evolving class. The cases are described in
terms of a stream written by some version of a class. When the stream
is read back by the same version of the class, there is no loss of
information or functionality. The stream is the only source of
information about the original class. Its class descriptions, while a
subset of the original class description, are sufficient to match up
the data in the stream with the version of the class being
reconstituted.
The descriptions are from the perspective of the stream being read in
order to reconstitute either an earlier or later version of the class.
In the parlance of RPC systems, this is a "receiver makes right"
system. The writer writes its data in the most suitable form and the
receiver must interpret that information to extract the parts it needs
and to fill in the parts that are not available.
5.6.1 Incompatible Changes
Incompatible changes to classes are those changes for which the
guarantee of interoperability cannot be maintained. The incompatible
changes that may occur while evolving a class are:
Deleting fields - If a field is deleted in a class, the stream written will not contain its value. When the stream is read by an
earlier class, the value of the field will be set to the default value
because no value is available in the stream. However, this default
value may adversely impair the ability of the earlier version to
fulfill its contract.
Moving classes up or down the hierarchy - This cannot be allowed since the data in the stream appears in the wrong sequence.
Changing a nonstatic field to static or a nontransient field to transient - When relying on default serialization, this change is
equivalent to deleting a field from the class. This version of the
class will not write that data to the stream, so it will not be
available to be read by earlier versions of the class. As when
deleting a field, the field of the earlier version will be initialized
to the default value, which can cause the class to fail in unexpected
ways.
Changing the declared type of a primitive field - Each version of the class writes the data with its declared type. Earlier versions of
the class attempting to read the field will fail because the type of
the data in the stream does not match the type of the field.
Changing the writeObject or readObject method so that it no longer writes or reads the default field data or changing it so that it
attempts to write it or read it when the previous version did not. The
default field data must consistently either appear or not appear in
the stream.
Changing a class from Serializable to Externalizable or vice versa is an incompatible change since the stream will contain data that is
incompatible with the implementation of the available class.
Changing a class from a non-enum type to an enum type or vice versa since the stream will contain data that is incompatible with the
implementation of the available class.
Removing either Serializable or Externalizable is an incompatible change since when written it will no longer supply the fields needed
by older versions of the class.
Adding the writeReplace or readResolve method to a class is incompatible if the behavior would produce an object that is
incompatible with any older version of the class.
5.6.2 Compatible Changes
The compatible changes to a class are handled as follows:
Adding fields - When the class being reconstituted has a field that does not occur in the stream, that field in the object will be
initialized to the default value for its type. If class-specific
initialization is needed, the class may provide a readObject method
that can initialize the field to nondefault values.
Adding classes - The stream will contain the type hierarchy of each object in the stream. Comparing this hierarchy in the stream with the
current class can detect additional classes. Since there is no
information in the stream from which to initialize the object, the
class fields will be initialized to the default values.
Removing classes - Comparing the class hierarchy in the stream with that of the current class can detect that a class has been deleted. In
this case, the fields and objects corresponding to that class are read
from the stream. Primitive fields are discarded, but the objects
referenced by the deleted class are created, since they may be
referred to later in the stream. They will be garbage-collected when
the stream is garbage-collected or reset.
Adding writeObject/readObject methods - If the version reading the stream has these methods then readObject is expected, as usual, to
read the required data written to the stream by the default
serialization. It should call defaultReadObject first before reading
any optional data. The writeObject method is expected as usual to call
defaultWriteObject to write the required data and then may write
optional data.
Removing writeObject/readObject methods - If the class reading the stream does not have these methods, the required data will be read by
default serialization, and the optional data will be discarded.
Adding java.io.Serializable - This is equivalent to adding types. There will be no values in the stream for this class so its fields
will be initialized to default values. The support for subclassing
nonserializable classes requires that the class supertype have a
no-arg constructor and the class itself will be initialized to default
values. If the no-arg constructor is not available, the
InvalidClassException is thrown.
Changing the access to a field - The access modifiers public, package, protected, and private have no effect on the ability of
serialization to assign values to the fields.
Changing a field from static to nonstatic or transient to nontransient - When relying on default serialization to compute the
serializable fields, this change is equivalent to adding a field to
the class. The new field will be written to the stream but earlier
classes will ignore the value since serialization will not assign
values to static or transient fields.
Never. You should organize yourself so that classes have the same serialVersionUID for their entire lifetime. You should (a) resist serialization-incompatible changes to the class; (b) write your own readObject()/writeObject()/readResolve()/writeReplace() objects so as to preserve the initial serialization format, and define an explicit serialVersionUID right at the beginning of the class's lifetime. The instant you change this value you have an enormous headache on your hands. Plan to avoid it.
From the JavaDoc of the Serializable interface:
The serialization runtime associates with each serializable class a version number, called a serialVersionUID, which is used during deserialization to verify that the sender and receiver of a serialized object have loaded classes for that object that are compatible with respect to serialization.
I think this is a good hint to answer your question: As soon as you change the class in a way, that serialization is affected (like adding/removing/changing serialized class members), then you really should change the value of serialVersionUID.

Categories

Resources