Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

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

Attack of the clones

Time and space considerations in four different approaches to implementing deep clone() methods

  • Print
  • Feedback

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: Cloning via Java reflection

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.

  • Print
  • Feedback

Resources