I was asked this question in an interview. The interviewer wanted to know how to make an object immutable. and then he asked what if I serialise this object - will it break immutability? If yes, how do I prevent it? Can anyone please help me understand this?
So, even though, the field which is pointing to Date or Collection or array object is final, you can still break the immutability of the class by breaking Encapsulation by returning a reference to the original mutable object.
It turns out you can serialize immutable objects because there's no requirement that there be a public no-argument constructor.
Serialization is a mechanism of converting the state of an object into a byte stream. Deserialization is the reverse process where the byte stream is used to recreate the actual Java object in memory. This mechanism is used to persist the object. The byte stream created is platform independent.
The only real disadvantage of immutable classes is that they require a separate object for each distinct value. Creating these objects can be costly, especially if they are large.
An immutable object is one which cannot be changed once created. You can create such an object by using private
access modifiers, and the final
keyword.
If an immutable object was serialized, its raw bytes could be modified so that upon deserialization the object is no longer the same.
This can't be prevented completely. Encryption, checksums, and CRC's will help to prevent this though.
You should read Effective Java written by Joshua Bloch. There is whole chapter about security issues connected with serialization and advices how to design your class properly.
In few words: you should learn about readObject and readResolve methods.
More detailed answer: Yes serialization can break immutability.
Let's assume you have class Period (it's example from Joshua's book):
private final class Period implements Serializable {
private final Date start;
private final Date end;
public Period(Date start, Date end){
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(this.start.compareTo(this.end() > 0)
throw new IllegalArgumentException("sth");
}
//getters and others methods ommited
}
It looks great. It's immutable (you can't change start and end after initialization), elegant, small, threadsafe etc.
But...
You have to remember that serialization is another way of creating objects (and it is not using constructors). Objects are build from byte stream.
Consider scenario when someone (attacker) change your serialization byte array. If he does such thing he could break your condition about start < end. Moreover there is possibility that attacker will put in stream (passed to deserialization method) reference to his Date object (which is mutable and Period class immutability will be completely destructed).
The best defense is not using serialization if you don't have to. If you have to serialize your class use Serialization Proxy pattern.
Edit (at kurzbot request): If you want to use Serialization Proxy you have to add static inner class inside Period. This class objects will be used for serialization instead of Period class objects.
In Period class write two new methods:
private Object writeReplace(){
return new SerializationProxy(this);
}
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Need proxy");
}
First method replace default serialized Period object with SerializationProxy object. Second guarantee that attacker won't use standard readObject method.
You should write writeObject method for SerializationProxy so you can use:
private Object readResolve() {
return new Period(start, end);
}
In that case you are using only public API and have certainty that Period class will remain immutably.
When you serialize an object graph that has multiple references to the same object, the serializer notes this fact, so that the deserialized object graph has the same structure.
For example,
int[] none = new int[0];
int[][] twoArrays = new int[] { none, none };
System.out.print(twoArrays[0] == twoArrays[1]);
will print true
, and if you serialized and deserialized twoArrays
then you would get the same result instead of each element of the array being a different object as in
int[][] twoDistinctArrays = new int[] { new int[0], new int[0] };
You can exploit this support for reference sharing to craft bytes after a serialized entry to share a reference with a privately help object or array, and then mutate it.
So an unserializable class can maintain invariants -- that a privately held object does not escape -- that a serializable class cannot maintain.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With