Unless special attention is given, copying objects typically results in shallow copies. Consider the following class:
import java.awt.Color; import java.awt.Point; public class Shape { private Color color; private Point center; public Shape(int x, int y, Color color) { this(new Point(x, y), color); } protected Shape(Point center, Color color) { this.center = center; this.color = color; } // ... }
The following code will create two references that point to the same shape object.
Shape s1 = new Shape(0, 0, Color.BLACK); Shape s2 = s1;
If we wanted two distict objects we could do the following:
Shape s1 = new Shape(0, 0, Color.BLACK); Shape s2 = new Shape(0, 0, Color.BLACK);
We now have two distinct objects that contain the same values. We can do this by just replicating the construction of the object. Now suppose we have an object s1
and we want to make a copy of it. We could add a constructor to the Shape
class to help with this:
public class Shape { // ... public Shape(Shape original) { this(original.center, original.color); } }
This constructor is called a copy constructor since it is designed to make a copy of the objet. Now we can make a copy of a shape by doing the following:
Shape s1 = new Shape(0, 0, Color.BLACK); Shape s2 = new Shape(s1);
Please note that these two techniques do not produce identical results. The following images show the results of running the debugger on the above code. Question: which code corresponds to which figure?1)
The problem with a shallow copy is that we haven't really made a copy of the object. We've just made a copy of the shell of the object, the attributes within the object are still shared by multiple objects. We can modify the copy constructor as follows in order ensure that a deep copy is made:
public class Shape { // ... public Shape(Shape original) { this(new Point(original.center), new Color(original.color.getRed(), original.color.getGreen(), original.color.getBlue(), original.color.getAlpha())); } }
Notice that here we are using a copy constructor from the Point
class to make a copy of that attribute (instead of just having the center
reference refer to the same object that the center
reference in original
refers to). We are also making a copy of the Color
object, but since the Color
class doesn't have a copy constructor, our approach is slightly different. The debugger confirms that s1
and s2
are now completely independent objects:
Why Do We Care About Deep Copies?
Suppose that the following methods are added to the Shape
class:
public class Shape { // ... public void setX(int x) { center.setLocation(x, center.getY()); } public void setY(int y) { center.setLocation(center.getX(), y); } public void setColor(Color color) { this.color = color; } @Override public String toString() { return "Shape at (" + center.getX() + ", " + center.getY() + ") with color: (" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")"; } }
Consider running the following code:
Shape s1 = new Shape(0, 0, Color.BLACK); Shape s2 = new Shape(s1); s1.setX(8); System.out.println(s1 + "\n" + s2);
Using our first attempt at a copy constructor will produce the following output:
Shape at (8.0, 0.0) with color: (0, 0, 0) Shape at (8.0, 0.0) with color: (0, 0, 0)
Clearly, the setX()
changed the center location for both shapes, while using our second attempt at a copy constructor ensures that the center location of only the first shape is changed:
Shape at (8.0, 0.0) with color: (0, 0, 0) Shape at (0.0, 0.0) with color: (0, 0, 0)
Copying with Class Hierarchies
Now suppose we have the following additional class:
import java.awt.Dimension; public class Rectangle extends Shape { protected Dimension size; public Rectangle(int x, int y, Color color, int width, int height) { super(x, y, color); size = new Dimension(width, height); } @Override public String toString() { String string = super.toString(); return "(" + size.getWidth() + ", " + size.getHeight() + ") Rectangle" + string.substring(5); } public void setSize(int width, int height) { size.setSize(width, height); } }
Similarly, we could create a copy constructor for the Rectangle
class that makes a deep copy of the object:
public class Rectangle extends Shape { //... public Rectangle(Rectangle original) { super(original); size = new Dimension(original.size); } }
Suppose we have the following program:
public static void main(String[] ignored) { String option = JOptionPane.showInputDialog(null, "Enter '1' for a shape, anything else for a rectangle"); if(option!=null || !option.isEmpty()) { Shape s1 = null; Shape s2 = null; if(option.charAt(0)=='1') { s1 = new Shape(0, 0, Color.BLACK); } else { s1 = new Rectangle(0, 0, Color.BLACK, 1, 1); } // Want to make a copy of s1 here // ... } }
We can make a copy of the shape, but it's a bit cumbersome because we need to figure out if we need to use the copy constructor for the Shape
or for the Rectangle
:
// Want to make a copy of s1 here if(s1 instanceof Shape) { s2 = new Shape(s1); } else { s2 = new Rectangle(s1); }
Some experts suggest that this is the best way to go about making deep copies of objects in Java; however, Java does provide a slightly more attractive option. We'll discuss this technique shortly, but using it allows us to simplify the copying process by replacing the above code with just one line:
// Want to make a copy of s1 here s2 = s1.clone();
The clone()
method should be implemented in such a way as to return a deep copy of the object used to call the method. When implemented correctly, this is ideal since we have an easy way to make a deep copy of any object, even if we don't know its specific type. Unfortunately, it's easy to implement clone
incorrectly, and many do, making it less reliable. The next section describes the correct way to implement clone
.
Cloneable and Object.clone()
The Object
class has a protected
method, clone()
which returns an Object
. Since the method is protected
, it is not available to the public unless a child class overrides it and makes it public.
If the creator of a class wishes to provide the clone functionality shown in the previous Shape
example, the following steps must be followed:
- Mark the
Shape
class as implementing theCloneable
interface. - Override the
clone
method declaring the method aspublic
.
Marking class as Cloneable
The Cloneable
interface is an interesting interface because it does not have any methods associated with it. It is a marker interface much like the Serializable
interface that is used to provide run-time information to the JVM. A CloneNotSupportedException
exception is thrown if Object
's clone()
method is called on an instance that does not implement the Cloneable
interface.
Overriding the clone()
Method
Here is an implementation of the Shape.clone()
method:
public class Shape implements Cloneable { // ... @Override public Shape clone() { Shape clone = null; try { clone = (Shape)super.clone(); clone.center = (Point)center.clone(); clone.color = new Color(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); } catch (CloneNotSupportedException e) { // shouldn't happen } return clone; } // ... }
The first thing we need to do within the try
block is to call the super constructor. All implementations of clone()
should do this. We want to do this so that we eventually make it all the way back to to Object.clone()
. From the Object
class javadoc:
The method clone for class Object performs a specific cloning operation. First, if the class of this object does not implement the interface Cloneable, then a CloneNotSupportedException is thrown. Note that all arrays are considered to implement the interface Cloneable. Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a "shallow copy" of this object, not a "deep copy" operation.
In this case, super.clone()
actually returns a Shape
object that is a shallow copy of the original object. In order to make it a deep copy, we need to make deep copies of all the attributes of the Shape
object. On the next line, we make a clone of the Point
object; however, we don't make a clone of the Color
. Since the Color
class doesn't implement the Cloneable
interface, we aren't able to call clone
. Instead, we use a constructor to create a copy of the color attribute.
Since the Shape
and Point
classes implement the Cloneable
interface, we should never end up in the catch
block; however, we need to have the try
/catch
block since Object.clone()
declares that it may throw a CloneNotSupportedException
exception.
If the Cloneable
interface is implemented for the super class, implementing clone()
in the subclass is straight forward:
public class Rectangle extends Shape { // ... @Override public Rectangle clone() { Rectangle clone = (Rectangle)super.clone(); clone.size = (Dimension)size.clone(); return clone; } // ... }
Shape
object, it produces the shallow copy.
2)Strictly speaking, this isn't completely a deep copy because we did not make a deep copy of the
color
attribute. It turns out that it is deep enough though. The reason for this is because the color
attribute is immutable (a constant). Since its value cannot be changed, there is no risk that changes to the attributes of s1
will inadvertently change the attributes of s2
.
Last modified: Monday, 29-Jul-2024 06:54:28 EDT