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

January 24, 2003

QWhat are the advantages and disadvantages of implementing deep cloning via Java serialization and a built-in Object.clone() method from a performance point of view?

AEquipping classes in your application with correct clone() implementation is essential to many defensive programming patterns. Common examples include defensive copying of method parameters, cloning internal fields before returning them in getters, implementing immutability patterns, implementing containers with deep cloning semantics, and so on.

Even though the question mentions just two possibilities, there are at least four distinct approaches to clone() implementation. In this Java Q&A installment, I consider design and performance tradeoffs involved in all of them.

Because cloning is so customizable, this article's examples will not necessarily translate directly to your own code; however, the general conclusions we will reach should provide useful guidelines in any application design.

Note the following disclaimer: What exactly constitutes a deep clone is debatable. Even though two objects can safely share the same String reference viewed as data, they cannot if the same field is used as an instance-scoped object monitor (such as when calling Object.wait()/notify() on it) or if field instance identity (such as when using the == operator) is significant to the design. In the end, whether or not a field is shareable depends on the class design. For simplicity, I assume below that all fields are used as pure data.

Performance measurements setup

Let's jump right into some code. I use the following simple hierarchy of classes as my cloning guinea pig:

public class TestBaseClass
             implements Cloneable, Serializable
{
    public TestBaseClass (String dummy)
    {
        m_byte = (byte) 1;
        m_short = (short) 2;
        m_long = 3L;
        m_float = 4.0F;
        m_double = 5.0;
        m_char = '6';
        m_boolean = true;
        m_int = 16;
        m_string = "some string in TestBaseClass";
        
        m_ints = new int [m_int];
        for (int i = 0; i < m_ints.length; ++ i) m_ints [i] = m_int;
        
        m_strings = new String [m_int];
        m_strings [0] = m_string; // invariant: m_strings [0] == m_string
        for (int i = 1; i < m_strings.length; ++ i)
            m_strings [i] = new String (m_string);
    }
    public TestBaseClass (final TestBaseClass obj)
    {
        if (obj == null) throw new IllegalArgumentException ("null input: obj");
        
        // Copy all fields:
        
        m_byte = obj.m_byte;
        m_short = obj.m_short;
        m_long = obj.m_long;
        m_float = obj.m_float;
        m_double = obj.m_double;
        m_char = obj.m_char;
        m_boolean = obj.m_boolean;
        
        m_int = obj.m_int;
        m_string = obj.m_string;
        
        if (obj.m_ints != null) m_ints = (int []) obj.m_ints.clone ();
        if (obj.m_strings != null) m_strings = (String []) obj.m_strings.clone ();
    }
    
    // Cloneable:
    public Object clone ()
    {
        if (Main.OBJECT_CLONE)
        {
            try
            {
                // Chain shallow field work to Object.clone(): 
                final TestBaseClass clone = (TestBaseClass) super.clone ();
                
                // Set deep fields:
                if (m_ints != null)
                    clone.m_ints = (int []) m_ints.clone ();
                if (m_strings != null)
                    clone.m_strings = (String []) m_strings.clone ();
                
                return clone;
            }
            catch (CloneNotSupportedException e)
            {
                throw new InternalError (e.toString ());
            }
        }
        else if (Main.COPY_CONSTRUCTOR)
            return new TestBaseClass (this);
        else if (Main.SERIALIZATION)
            return SerializableClone.clone (this);
        else if (Main.REFLECTION)
            return ReflectiveClone.clone (this);
        else
            throw new RuntimeException ("select cloning method");
    }
    
    protected TestBaseClass () {} // accessible to subclasses only
    
    private byte m_byte;
    private short m_short;
    private long m_long;
    private float m_float;
    private double m_double;
    private char m_char;
    private boolean m_boolean;
    private int m_int;
    private int [] m_ints;
    private String m_string;
    private String [] m_strings; // invariant: m_strings [0] == m_string    
} // end of class
public final class TestClass extends TestBaseClass
             implements Cloneable, Serializable
{
    public TestClass (String dummy)
    {
        super (dummy);
        
        m_int = 4;
        
        m_object1 = new TestBaseClass (dummy);
        m_object2 = m_object1; // invariant: m_object1 == m_object2
    
        m_objects = new Object [m_int];
        for (int i = 0; i < m_objects.length; ++ i)
            m_objects [i] = new TestBaseClass (dummy);
    }
    public TestClass (final TestClass obj)
    {
        // Chain to super copy constructor:
        super (obj);
        
        // Copy all fields declared by this class:
        
        m_int = obj.m_int;
        
        if (obj.m_object1 != null)
            m_object1 = ((TestBaseClass) obj.m_object1).clone ();
        m_object2 = m_object1; // preserve the invariant
        
        if (obj.m_objects != null)
        {
            m_objects = new Object [obj.m_objects.length];
            for (int i = 0; i < m_objects.length; ++ i)
                m_objects [i] = ((TestBaseClass) obj.m_objects [i]).clone ();
        }
    }
    
    // Cloneable:
    public Object clone ()
    {
        if (Main.OBJECT_CLONE)
        {
            // Chain shallow field work to Object.clone():
            final TestClass clone = (TestClass) super.clone ();
            
            // Set only deep fields declared by this class:
            
            if (m_object1 != null)
                clone.m_object1 = ((TestBaseClass) m_object1).clone ();
            clone.m_object2 = clone.m_object1; // preserve the invariant
            
            if (m_objects != null)
            {
                clone.m_objects = (Object []) m_objects.clone ();
                for (int i = 0; i < m_objects.length; ++ i)
                    clone.m_objects [i] = ((TestBaseClass) m_objects [i]).clone ();
            }
            return clone;
        }
        else if (Main.COPY_CONSTRUCTOR)
            return new TestClass (this);
        else if (Main.SERIALIZATION)
            return SerializableClone.clone (this);
        else if (Main.REFLECTION)
            return ReflectiveClone.clone (this);
        else
            throw new RuntimeException ("select cloning method");
    }
        
    protected TestClass () {} // accessible to subclasses only
    private int m_int;        
    private Object m_object1, m_object2; // invariant: m_object1 == m_object2
    private Object [] m_objects;
} // End of class


TestBaseClass has several fields of primitive types as well as a String and a couple of array fields. TestClass both extends TestBaseClass and aggregates several instances of it. This setup allows us to see how inheritance, member object ownership, and data types can affect cloning design and performance.

  • Print
  • Feedback

Resources