Recently I found out that some classes compiled by Java (Java 8) and by ajc (v.1.9.2) are not serialization-compatible. By serialization-compatibility I mean that calculated default serialVersionUID are not same.
Example:
public class Markup implements Serializable {
private final MyUnit unit;
public Markup(MyUnit unit) { this.unit = unit; }
public enum MyUnit { DOUBLE, STRING }
public static void main(String[] args) throws IOException, ClassNotFoundException {
Path path = Paths.get("markup.bin");
if (args.length == 0) {
try (OutputStream fileOutput = Files.newOutputStream(path);
ObjectOutputStream objectOutput = new ObjectOutputStream(fileOutput))
{
objectOutput.writeObject(new Markup(MyUnit.STRING));
}
} else {
try (InputStream fileInput = Files.newInputStream(path);
ObjectInputStream objectInput = new ObjectInputStream(fileInput))
{
System.out.println(objectInput.readObject());
}
}
}
static String switchType(MyUnit unit) {
switch (unit) {
case STRING: return "%";
case DOUBLE: return "p";
default: return "Undefined";
}
}
}
When I compile this class by ajc and run, and then compile this class by javac and run I get exception about incompatibility of serialization format:
Exception in thread "main" java.io.InvalidClassException: Markup; local class incompatible: stream classdesc serialVersionUID = -1905477862550005139, local class serialVersionUID = 793529206923536473
I also found out that's it's because of ajc switch code generator. It creates additional field in class private static int[] $SWITCH_TABLE$Markup$MyUnit
Fields generated by javac: Fields generated by ajc:
My questions are:
private static
affects generation of serialVersionUID ?1. Is it allowed by specification for java compiler to generate fields which are not defined in the class?
Yes, though they should be marked as synthetic. Synthetics include accessors for inner classes (although that implementation has changed recently), lambda expressions methods and, I think, default constructors.
2. Why ajc generates additional field? Some sort of performance optimization?
Looks like a bad performance optimisation. Perhaps it's something to do with adding aspects efficiently.
3. Are there any way to make ajc to not generate additional field?
No idea. You should expect synthetics to be generated regardless.
4. What the reason why private static affects generation of serialVersionUID ?
Java Serialization was created in "Internet time". For compatibility, we are left with whatever the first version happened to do. Moral: If you create anything in Internet time, do throw it away.
5. Are developers of aspectj aware of this behavior? If so, why they choose generate field anyway?
I would hope so. Compilers are expected to genera synthetics.
6. Are there any guarantees how Java class will be serialized by JLS?
There is a Java Serialization specification (though I don't really expect many people to read it).
7. How Javac-generated code works without this field?
It's relatively easy to see how you could write a switch-on-string without an array. Exact details of an ugly optimised version are going to be messy. You can see what actually happens with javap -private -c
.
Conclusion
It is recommended to add a serialVersionUID
if data is expected to be used between different versions of a class. OTOH, it's also recommended to not use Java Serialization.