Efficient way to clone a generic object in Java preferably without a factory
Question
I find my self having to export references to keys in an underlying Java collections
map. I don't want to give out references to the actual key objects but copies of them to ensure that nobody fiddles with the values and messes up the map. So I need to create a copy of an arbitrary generic type.
I already wrote a utility class that does this through in memory serialisation, but then noticed that Collections.nCopies
also gives me copies of things.
So just to give some idea of what I am doing, I need to for instance access the next (higher/lower) key value given some other key value.
public class MapWrapper<T, K extends Comparable<K>>
implements OtherThing<T, K>
{
public K next (K current) {
return ... cloned next highest key as per NavigableMap.higherKey()
}
}
So the question is if it is better to copy the objects with
T copy = Collections.nCopies(1,item).get(0)
or to serialise the object and then deserialise it
final ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
final ObjectOutputStream outputStream = new ObjectOutputStream(outputBuffer);
outputStream.writeObject(item);
outputStream.close();
final ByteArrayInputStream inputBuffer = new ByteArrayInputStream(buffer);
final ObjectInputStream inputStream = new ObjectInputStream(inputBuffer);T copy = null;
T item = null;
try {
copy = (T)inputStream.readObject();
} catch (ClassNotFoundException e) {
Logger.getLogger(SerialisationService.class.getName()).log(Level.WARNING, null, e);
}
inputStream.close();
inputBuffer.close();
outputBuffer.close();
A quick test with copying a Long
shows that the Collections.nCopies
is faster (0 ms / 8 ms). Is there some caveat to that method that I'm not able to think about at this late hour though, or can you think of an even better way of getting a copy of the keys?
Solution
There is a significant difference - Collections.nCopies
doesn't copy the object! It just returns a List
containing n
entries, all of which are the provided object (check the source!). Any changes made to anything you pick out from that list will be reflected in the original object. So this code:
Point original = new Point(0, 0);
Point supposedCopy = Collections.nCopies(1, original).get(0);
supposedCopy.x = 13;
System.out.println("x = " + original.x);
will print 13
, not 0
. Oops.
A serialization roundtrip isn't a bad idea. An alternative is using Cloneable
, which unfortunately is kind of broken. Cloneable
doesn't declare clone()
, but it is declared as protected
in Object
, so you have to invoke it by means of reflection.
Personally, I'd prefer to use immutable objects in your situation, if possible.