|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 7
One more interesting advantage of Approach 3 is that it can preserve the structure of object graph rooted at the source object. Examine the dummy TestBaseClass constructor. It fills the entire m_strings array with the same m_string reference. Without any special effort on our part, the invariant m_strings[0] == m_string is preserved in the cloned object. In Approaches 1 and 2, the same effect is either purely incidental (such as when immutable
objects remain shared by reference) or requires explicit coding (as with m_object1 and m_object2 in TestClass). The latter could be hard to get right in general, especially when object identities are established at runtime and not
compile time (as is the case with TestClass).
Approach 4 draws inspiration from Approach 3. Anything that uses reflection can work on a variety of classes in a generic
way. If I require the class in question to have a (not necessarily public) no-arg constructor, I can easily create an empty
instance using reflection. It is especially efficient when the no-arg constructor doesn't do anything. Then it is a straightforward
matter to walk the class's inheritance chain all the way to Object.class and set all (not just public) declared instance fields for each superclass in the chain. For each field, I check whether
it contains a primitive value, an immutable object reference, or an object reference that needs to be cloned recursively.
The idea is straightforward but getting it to work well requires handling a few details. My full demo implementation is in
class ReflectiveClone, available as a separate download. Here is the pseudo-code of the full implementation, with some details and all error handling omitted for simplicity:
public abstract class ReflectiveClone
{
/**
* Makes a reflection-based deep clone of 'obj'. This method is mutually
* recursive with {@link #setFields}.
*
* @param obj current source object being cloned
* @return obj's deep clone [never null; can be == to 'obj']
*/
public static Object clone (final Object obj)
{
final Class objClass = obj.getClass ();
final Object result;
if (objClass.isArray ())
{
final int arrayLength = Array.getLength (obj);
if (arrayLength == 0) // empty arrays are immutable
return obj;
else
{
final Class componentType = objClass.getComponentType ();
// Even though arrays implicitly have a public clone(), it
// cannot be invoked reflectively, so need to do copy construction:
result = Array.newInstance (componentType, arrayLength);
if (componentType.isPrimitive () ||
FINAL_IMMUTABLE_CLASSES.contains (componentType))
{
System.arraycopy (obj, 0, result, 0, arrayLength);
}
else
{
for (int i = 0; i < arrayLength; ++ i)
{
// Recursively clone each array slot:
final Object slot = Array.get (obj, i);
if (slot != null)
{
final Object slotClone = clone (slot);
Array.set (result, i, slotClone);
}
}
}
return result;
}
}
else if (FINAL_IMMUTABLE_CLASSES.contains (objClass))
{
return obj;
}
// Fall through to reflectively populating an instance created
// via a no-arg constructor:
// clone = objClass.newInstance () can't handle private constructors:
Constructor noarg = objClass.getDeclaredConstructor (EMPTY_CLASS_ARRAY);
if ((Modifier.PUBLIC & noarg.getModifiers ()) == 0)
{
noarg.setAccessible (true);
}
result = noarg.newInstance (EMPTY_OBJECT_ARRAY);
for (Class c = objClass; c != Object.class; c = c.getSuperclass ())
{
setFields (obj, result, c.getDeclaredFields ());
}
return result;
}
/**
* This method copies all declared 'fields' from 'src' to 'dest'.
*
* @param src source object
* @param dest src's clone [not fully populated yet]
* @param fields fields to be populated
*/
private static void setFields (final Object src, final Object dest,
final Field [] fields)
{
for (int f = 0, fieldsLength = fields.length; f < fieldsLength; ++ f)
{
final Field field = fields [f];
final int modifiers = field.getModifiers ();
if ((Modifier.STATIC & modifiers) != 0) continue;
// Can also skip transient fields here if you want reflective
// cloning to be more like serialization.
if ((Modifier.FINAL & modifiers) != 0)
throw new RuntimeException ("cannot set final field" +
field.getName () + " of class " + src.getClass ().getName ());
if ((Modifier.PUBLIC & modifiers) == 0) field.setAccessible (true);
Object value = field.get (src);
if (value == null)
field.set (dest, null);
else
{
final Class valueType = value.getClass ();
if (! valueType.isPrimitive () &&
! FINAL_IMMUTABLE_CLASSES.contains (valueType))
{
// Value is an object reference, and it could be either an
// array or of some mutable type: try to clone it deeply
// to be on the safe side.
value = clone (value);
}
field.set (dest, value);
}
}
}
private static final Set FINAL_IMMUTABLE_CLASSES; // Set in <clinit>
private static final Object [] EMPTY_OBJECT_ARRAY = new Object [0];
private static final Class [] EMPTY_CLASS_ARRAY = new Class [0];
static
{
FINAL_IMMUTABLE_CLASSES = new HashSet (17);
// Add some common final/immutable classes:
FINAL_IMMUTABLE_CLASSES.add (String.class);
FINAL_IMMUTABLE_CLASSES.add (Byte.class);
...
FINAL_IMMUTABLE_CLASSES.add (Boolean.class);
}
} // End of class
Note the use of java.lang.reflect.AccessibleObject.setAccessible() to gain access to nonpublic fields. Of course, this requires sufficient security privileges.