A guide to object cloning in java

A clone is an exact copy of the original. In java, it essentially means the ability to create an object with similar state as the original object. The clone() method provides this functionality. In this post, we will explore most of the important aspects of java cloning.

Explaining cloning in detail

So cloning is about creating the copy of original object. Its dictionary meaning is : “make an identical copy of“.

By default, java cloning is ‘field by field copy’ i.e. as the Object class does not have idea about the structure of class on which clone() method will be invoked. So, JVM when called for cloning, do following things:

1) If the class has only primitive data type members then a completely new copy of the object will be created and the reference to the new object copy will be returned.
2) If the class contains members of any class type then only the object references to those members are copied and hence the member references in both the original object as well as the cloned object refer to the same object.

Apart from above default behavior, you can always override this behavior and specify your own. This is done using overriding clone() method. Lets see how it is done.

Java infrastructure for cloning

Every language which supports cloning of objects has its own rules and so does java. In java, if a class needs to support cloning it has to do following things:

A) You must implement Cloneable interface.
B) You must override clone() method from Object class. [Its weird. clone() method should have been in Cloneable interface.]

Java docs about clone() method are given below (formatted and extract).

/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object  [More ...] clone() throws CloneNotSupportedException;

First statement guarantees that cloned object will have separate memory address assignment.
Second statement suggest that original and cloned objects should have same class type, but it is not mandatory.
Third statement suggest that original and cloned objects should have be equal using equals() method, but it is not mandatory.

Lets see things in actual code. Lets hit the keyboard.

Our first class is Employee class with 3 attributes. Id, name and department.

public class Employee implements Cloneable{

    private int empoyeeId;
    private String employeeName;
    private Department department;

    public Employee(int id, String name, Department dept)
    {
        this.empoyeeId = id;
        this.employeeName = name;
        this.department = dept;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    //Accessor/mutators methods will go there
}

Our Department class has two attributes. id and name.

public class Department
{
    private int id;
    private String name;

    public Department(int id, String name)
    {
        this.id = id;
        this.name = name;
    }
    //Accessor/mutators methods will go there
}

So, if we need to clone the Employee class, then we need to do something like this.

public class TestCloning {

    public static void main(String[] args) throws CloneNotSupportedException
    {
        Department dept = new Department(1, "Human Resource");
        Employee original = new Employee(1, "Admin", dept);
        //Lets create a clone of original object
        Employee cloned = (Employee) original.clone();
        //Let verify using employee id, if cloning actually workded
        System.out.println(cloned.getEmpoyeeId());

        //Verify JDK's rules

        //Must be true and objects must have different memory addresses
        System.out.println(original != cloned);

        //As we are returning same class; so it should be true
        System.out.println(original.getClass() == cloned.getClass());

        //Default equals method checks for refernces so it should be false. If we want to make it true,
        //we need to override equals method in Employee class.
        System.out.println(original.equals(cloned));
    }
}

Output:

1
true
true
false

Great, we successfully cloned the Employee object. But, remember we have two references of same object and now both will change the state of object in different parts of application. Want to see how?

Lets see:

public class TestCloning {

    public static void main(String[] args) throws CloneNotSupportedException {
        Department hr = new Department(1, "Human Resource");
        Employee original = new Employee(1, "Admin", hr);
        Employee cloned = (Employee) original.clone();

        //Let change the department name in cloned object and we will verify in original object
        cloned.getDepartment().setName("Finance");

        System.out.println(original.getDepartment().getName());
    }
}

Output:

Finance

Oops, cloned object changes are visible in original also. This way cloned objects can make havoc in system if allowed to do so. Anybody can come and clone your application objects and do whatever he likes. Can we prevent this??

Answer is yes, we can. We can prevent this using deep cloning and use copy constructors. We will learn about them later in this post.

Shallow Cloning

This is default implementation in java. In overridden clone method, if you are not cloning all the object types (not primitives), then you are making a shallow copy.

All above examples are of shallow copy only, because we have not cloned the Department object on Employee class’s clone method. Now, i will move on to next section where we will see the deep cloning.

Deep cloning

It is the desired behavior in most the cases. We want a clone which is independent of original and making changes in clone should not affect original.

Let see how it can be done in our case.

//Modified clone() method in Employee class
@Override
protected Object clone() throws CloneNotSupportedException {
    Employee cloned = (Employee)super.clone();
    cloned.setDepartment((Department)cloned.getDepartment().clone());
    return cloned;
}

I modified the Employee classes clone() method and added following clone method in Department class.

//Defined clone method in Department class.
@Override
protected Object clone() throws CloneNotSupportedException {
    return super.clone();
}

Now testing our cloning code gives desired result and name of department will not be modified.

public class TestCloning {
    public static void main(String[] args) throws CloneNotSupportedException {
        Department hr = new Department(1, "Human Resource");
        Employee original = new Employee(1, "Admin", hr);
        Employee cloned = (Employee) original.clone();

        //Let change the department name in cloned object and we will verify in original object
        cloned.getDepartment().setName("Finance");

        System.out.println(original.getDepartment().getName());
    }
}

Output:

Human Resource

Here, changing state of the cloned object does not affect the original object.

So deep cloning requires satisfaction of following rules.

  • No need to separately copy primitives.
  • All the member classes in original class should support cloning and in clone method of original class in context should call super.clone() on all member classes.
  • If any member class does not support cloning then in clone method, one must create a new instance of that member class and copy all its attributes one by one to new member class object. This new member class object will be set in cloned object.

Using copy constructors

Copy constructors are special constructors in a class which takes argument for its own class type. So, when you pass an instance of class to copy constructor, then constructor will return a new instance of class with values copied from argument instance. Lets see this in example:

public class PointOne {
    private Integer x;
    private Integer y;

    public PointOne(PointOne point){
        this.x = point.x;
        this.y = point.y;
    }
}

This method looks simple and it is until comes inheritance. When you define a class by extending above class, you need to define a similar constructor there also. In child class, you need to copy child specific attributes and pass the argument to super class’s constructor. Lets see how?

public class PointTwo extends PointOne{
    private Integer z;

    public PointTwo(PointTwo point){
        super(point); //Call Super class constructor here
        this.z = point.z;
    }
}

So, are we fine now? NO. The problem with inheritance is that exact behavior is identified only in run time. So, in our case if some class passed the instance of PointTwo in constructor of PointOne. In this case, you will get the instance of PointOne in return where you passed instance of PointTwo as argument. Lets see this in code:

class Test
{
    public static void main(String[] args)
    {
        PointOne one = new PointOne(1,2);
        PointTwo two = new PointTwo(1,2,3);

        PointOne clone1 = new PointOne(one);
        PointOne clone2 = new PointOne(two);

        //Let check for class types
        System.out.println(clone1.getClass());
        System.out.println(clone2.getClass());
    }
}

Output:

class corejava.cloning.PointOne
class corejava.cloning.PointOne

Another way of creating a copy constructor is to have static factory methods. They take class type in argument and create a new instance using another constructor of class. Then these factory methods will copy all the state data to new class instance just created in previous step, and return this updated instance.

public class PointOne implements Cloneable{
    private Integer x;
    private Integer y;

    public PointOne(Integer x, Integer y)
    {
        this.x = x;
        this.y = y;
    }

    public PointOne copyPoint(PointOne point) throws CloneNotSupportedException
    {
        if(!(point instanceof Cloneable))
        {
            throw new CloneNotSupportedException("Invalid cloning");
        }
        //Can do multiple other things here
        return new PointOne(point.x, point.y);
    }
}

Cloning with serialization

This is another easy way of deep cloning. In this method, you just serialize the object to be cloned and de-serialize it. Obviously, the object which need to be cloned should implement Serializable interface.

Before going any further, I should caution that this technique is not to be used lightly. First of all, serialization is hugely expensive. It could easily be a hundred times more expensive than the clone() method. Secondly, not all objects are Serializable. Thirdly, making a class Serializable is tricky and not all classes can be relied on to get it right.

Lets see the code sample:

@SuppressWarnings("unchecked")
public static  T clone(T t) throws Exception {
    //Check if T is instance of Serializeble other throw CloneNotSupportedException
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    //Serialize it
    serializeToOutputStream(t, bos);
    byte[] bytes = bos.toByteArray();
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));

    //Deserialize it and return the new instance
    return (T)ois.readObject();
}

Cloning using Apache commons

Apache commons has also utility function for deep cloning. Below is sample usage of cloning facility using Apache commons:

SomeObject cloned = org.apache.commons.lang.SerializationUtils.clone(someObject);

Best practices

1) When you don’t know whether you can call the clone() method of a particular class as you are not sure if it is implemented in that class, you can check with checking if the class is instance of “Cloneable” interface as below.

if(obj1 instanceof Cloneable){
    obj2 = obj1.clone();
}

//Dont do this. Cloneabe dont have any methods
obj2 = (Cloneable)obj1.clone();

2) No constructor is called on the object being cloned. As a result, it is your responsibility, to make sure all the members have been properly set. Also, if you are keeping track of number of objects in system by counting the invocation of constructors, you got a new additional place to increment the counter.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ defines a class DateV3 with the following: private member variables: int year, month, day; Has three constructors and one destructor as follows: The first constructor takes three parameters, int y, int m, int n; The second is the copy constructor that takes a DateV3 object as the parameter; The third is the default constructor that takes no parameter; The destructor takes no parameter. (3) Has overloaded operators: int operator-(DateV3 & oneDate); // return difference in days between the calling object and oneDate DateV3 operator+(int inc); // return a Date object that is inc days later than the calling object DateV3 operator-(int dec); // return a Date object that is dec days earlier than the calling object DateV3 operator++(); // overload the prefix ++ operator DateV3 operator++(int); // overload the postfix ++ operator friend ostream& operator<< (ostream& outputStream, DateV3& theDate); // overload the << operator Test class DateV3 in the main function as follows: Declare and initialize an object to represent today, which should be the date that you work on this assignment.Declare and initialize an object to represent your OWN birthday.Express John’s birthday given John is 5 days older than yours. Create Tom’s birthday by using the copy constructor, assuming Tom has the same birthday as you. Display how many days have passed since your birth, John’s birth, and Tom’s birth, respectively. Create an DateV3 object, someday, by cloning Tom’s birthday. Increment someday by the prefix operator ++ once, and by postfix operator ++ once.Display someday, today, your birthday, John’s birthday, and Tom’s birthday. Declare a DateV3 object to represent 28 February 2024, display it, apply the prefix ++ operator on it, display it again, and apply the postfix ++ operator on it and display it again.
06-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值