What’s correct and what’s not correct about the Java deserialization process?
You are working to enhance a legacy application, in particular to add serialization support and add more constraints to new business objects. The legacy application class looks like this.
public class Person {
private String name = null;
public Person() {}
public Person(String s) {
name = s;
}
}
You’ve also added a new application class.
public class EnhancedPerson extends Person implements Serializable {
public EnhancedPerson(String s) {
super(s);
if (s == null || s.length() == 0) {
throw new IllegalArgumentException("Invalid name");
}
}
}
Which statement is correct? Choose one.
A. The EnhancedPerson class may not participate in serialization because its superclass is not serializable.
B. The EnhancedPerson class may not participate in serialization because it does not have a zero-argument constructor.
C. The EnhancedPerson class can be serialized but cannot be deserialized.
D. Immediately after deserialization, the name field of an EnhancedPerson will have a null value.
E. The EnhancedPerson class will always throw IllegalArgumentException during deserialization.
Answer. This question investigates the deserialization process of an object that has a nonserializable parent type. Of course, every object has a nonserializable parent because, even if nothing else were true, java.lang.Object itself falls into this category.
This first observation tells you that option A must be incorrect because a nonserializable parent type is inevitable and, therefore, cannot possibly prevent serialization and deserialization.
Two more rules regarding the serialization system are relevant to answering this question.
- When a serializable object is deserialized, the nonserializable parent aspects are initialized by calling an accessible zero-argument constructor in the immediate parent that’s not serializable.
- Except for record types, deserialization does not use any of the constructor or initialization code for the elements of that object hierarchy that are serializable.
From those two points, you can see that option B is incorrect.
Option C is also incorrect because nothing in the code prevents deserialization. If the zero-argument constructor of Person were private, or if it did not exist, this option would be correct.
Option D is correct because the name field belongs to the Person aspect of the EnhancedPerson object. Such fields are not part of the serialized information. As noted earlier, when the Person instance is re-created, this is done by calling the zero-argument constructor of Person. Because of this, the name field will be initialized to null. Of course, this would also be the case if the assignment of null were not present in the code, because the memory allocated for an object is always zeroed before it becomes available for use as the new object.
Regarding why option E is incorrect, recall that the deserialization process for (nonrecord) classes does not call any constructor in the Serializable aspects of the object. This means that the code in the EnhancedPerson constructor that validates the supplied name will not be called; therefore, no exception can be thrown.
This discussion raises questions. In the current design, deserialization creates an EnhancedPerson object with a null name field. The validation in the constructor is clearly intended to make this impossible. There would be aspects to address in restoring this integrity.
One aspect would be to ensure that the name field is immutable (the Person class currently has no setters and name is private, but the class is clearly a skeleton because it has no usable methods). Alternatively, you could ensure that all changes are controlled by the EnhancedPerson in a way that keeps it valid.
The second aspect, which is more relevant to this question, would be to ensure that any EnhancedPerson object deserialized from a data stream conforms to the validity rules. This can be accomplished by providing a private readObject method. This method allows you to take control of the deserialization process. In the following example, the object is read from the stream using the default mechanism, but then it’s checked to determine if it is valid. If it’s not valid, the exception that’s thrown will cause the deserialization to be abandoned.
private void readObject(ObjectInputStream ois) throws
IOException,
ClassNotFoundException {
ois.defaultReadObject();
if (name == null || name.length() == 0) {
throw new InvalidObjectException("Invalid name");
}
}
Conclusion. The correct answer is option D.
Source: oracle.com