You see it everywhere in Java code, mostly in classes that are near to persistence layer:
@SuppressWarnings("serial")
public class XXX implements Serializable ....
We are used to overlook such noise, also called "boilerplate code". The more noise, the higher the risk that something is NOT noise and we miss it. Can we avoid it, and produce source code that is written for humans and not for compilers?
Just to be clear: we do not want to write less code (that does more). We want to write less boilerplate code.
Yes, we can. At least for @SuppressWarnings("serial")
.
Don't write it, simply leave it out. Tell your IDE or compiler to ignore such warnings.
It is really time to produce code that reads like a novel, not like a video recorder manual.
You do yourself a favor, and all your colleagues that need to read your code at some time.
It is not a risk to disable @SuppressWarnings("serial")
. This Blog will try to explain why.
Serializable
Serializable
is a Java core interface that you must implement when you intend to write
instances of your class to a disk file, or into a database BLOB,
or when you need to drag & drop one of your Java objects.
The serial representation of the object is unreadable ("binary").
It also contains all member field objects, recursively.
Just transient
and static
fields are not serialized.
When you serialize an object that doesn't implement Serializable
, or one of its fields' types does not,
you'll get a NotSerializableException
at runtime. So the compiler does not check this!
Here is a serialization / deserialization demo, using just java.io
classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | public class SerializableConfiguration implements Serializable { public static final String configurationExtension = ".config"; private Long customerId; private transient String state; public void setCustomerId(Long customerId) { this.customerId = customerId; } public void setState(String state) { this.state = state; } @Override public String toString() { return "customerId="+customerId+", state="+state; } public static void main(String[] args) throws IOException, ClassNotFoundException { final Long customerId = 12345L; final String state = "running"; final SerializableConfiguration configuration = new SerializableConfiguration(); configuration.setCustomerId(customerId); configuration.setState(state); System.out.println("Expecting: "+configuration); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ObjectOutputStream objectOut = new ObjectOutputStream(out); objectOut.writeObject(configuration); objectOut.close(); final InputStream in = new ByteArrayInputStream(out.toByteArray()); final ObjectInputStream objectIn = new ObjectInputStream(in); final Object deserializedObject = objectIn.readObject(); System.out.println("Result: "+deserializedObject); } } |
This test class contains a static
, a normal, and a transient
field.
The static
field is connected to the class and does not appear in the serialized object,
also the transient
field is ignored. Just the customerId
gets serialized.
The main()
procedure demonstrates this by serializing into and deserializing from a byte array.
Most JDK classes like String, Integer, Date
...
are Serializable
, even the Swing classes.
When you run this demo class, it will output:
Expecting: customerId=12345, state=running
Result: customerId=12345, state=null
... proving that transient
fields are not serialized.
The Compiler Warning
Eclipse gives me following warning about the class above:
The serializable class SerializableConfiguration does not declare a static final serialVersionUID field of type long
Reason is that it assumes that I will care about the version-id of this class, and change it as soon as the class gets incompatible with its previous version. That will take place when I remove a property, or change the name of a property, or do that in any class contained in it.
Because then the new class could not match the objects stored at customer site any more.
An exception would be thrown when loading a serialized object into a class that is different from the one it was created from.
This will not be detected by the compiler, and maybe all unit tests would be green before shipping that runtime-bug to the customer.
I really should care about that. But not by writing @SuppressWarnings("serial")
, and not by manually maintaining
all these serialVersionUID
fields.
Makes No Sense
I admit that, in limited cases, maintaining the serialVersionUID
could be useful.
But I doubt that developers can cope with that, and change the serialVersionUID
at the right time.
But even when they succeed in doing so, they would not prevent the customer crash by that,
because the incompatible stored objects would need to be converted.
And when they do not care, it also crashes when the class gets incompatible, although it always has the same serialVersionUID
.
Consider not caring about the serialVersionUID
.
The compiler would automatically generate a new serialVersionUID
for that class any time it was actually changed.
No developer needs to know and take care of the version compatibility.
At the customer site, of course, it will crash when the new serialVersionUID
does not match the stored one. Like above.
So what is the difference? Why does the compiler recommend to maintain the serialVersionUID
?
There is a very small difference, for a very high price:
- the application upgrade could survive the incompatible serialized class when properties were just added, not changed or removed
- the price is additional expert work for developers, and a high risk that this goes wrong anyway.
I'm really tired of seeing this useless annotation everywhere. Better use XML serialization, this is more readable, and take adequate actions when catching a deserialization-exception (e.g. resetting to defaults).
Keine Kommentare:
Kommentar veröffentlichen