Implementation of java deep copy

Posted by blueman378 on Fri, 23 Aug 2019 16:43:25 +0200

In some business scenarios, we need two identical but unrelated java objects. For example, using prototype mode, multi-threaded programming, etc. java provides the concept of deep copy. Through deep copy, a target object can be perfectly copied from the source object, which is the same but independent of the source object. Same here means that two objects have the same state and action. Independence means that changing the state of one object does not affect the other object. There are two common implementations for deep copy: Serializable and Cloneable.
Serializable is a common way to realize object copy through serialization and deserialization of java objects. Originally, all java objects are in the virtual machine heap. By serialization, the information of source objects is stored outside the heap in another form. At this point, there are two copies of information about the source object, one in the heap and one out of the heap. Then the information outside the heap is put back into the heap by deserialization, and a new object, the target object, is created.
Serializable code

public static Object cloneObjBySerialization(Serializable src)
    {
        Object dest = null;
        try
        {
            ByteArrayOutputStream bos = null;
            ObjectOutputStream oos = null;
            try
            {
                bos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(bos);
                oos.writeObject(src);
                oos.flush();
            }
            finally
            {
                oos.close();
            }
            byte[] bytes = bos.toByteArray();
            ObjectInputStream ois = null;
            try
            {
                ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
                dest = ois.readObject();
            }
            finally
            {
                ois.close();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();//Cloning failure
        }
        return dest;
    }

Source object type and member object type need to implement Serializable interface, one can not be less.

import java.io.Serializable;

public class BattleShip implements Serializable
{
    String name;
    ClonePilot pilot;
    BattleShip(String name, ClonePilot pilot)
    {
        this.name = name;
        this.pilot = pilot;
    }
}
//ClonePilot Type implementation Cloneable Interface, but this passes through Serializable The way the object is copied has no effect
public class ClonePilot implements Serializable,Cloneable
{
    String name;
    String sex;
    ClonePilot(String name, String sex)
    {
        this.name = name;
        this.sex = sex;
    }
    public ClonePilot clone()
    {
        try
        {
            ClonePilot dest = (ClonePilot)super.clone();
            return dest;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
}

Finally, execute the test code to see the results.

public static void main(String[] args)
{ BattleShip bs
= new BattleShip("Dominix", new ClonePilot("Alex", "male")); System.out.println(bs); System.out.println(bs.name + " "+bs.pilot.name); BattleShip cloneBs = (BattleShip)CloneObjUtils.cloneObjBySerialization(bs); System.out.println(cloneBs); System.out.println(cloneBs.name + " "+cloneBs.pilot.name); }
console--output--

  cloneObject.BattleShip@154617c

  Dominix Alex

  cloneObject.BattleShip@cbcfc0

  Dominix Alex

  cloneObject.ClonePilot@a987ac

  cloneObject.ClonePilot@1184fc6

As you can see from the console output, two different BattleShip objects refer to different Clonepilot objects. String is an immutable class, which can be treated as a basic type. The data is available, and neither BattleShip object refers to the same member object. The deep copy is successful.

Note that serialization ignores variables that are transiently modified. So this approach does not copy transiently modified variables.

Another way is Cloneable, the core of which is the native method clone() of the Object class. By calling the clone method, you can create a clone of the current object, but it should be noted that this method does not support deep copy. If the member variable of the object is the basic type, that's fine. But for variables or collections of custom types (collections I haven't tested yet) and arrays, there's a problem. You will find that the source object and the target object's custom type member variables are the same object, that is, the shallow copy, which is the copy of the object reference (address). In this way, the source object and the target object are not independent of each other, but entangled endlessly. In order to make up for this shortcoming of clone method. We need to deal with deep copies of non-basic type member variables ourselves.
--Cloneable code

public class Cruiser implements Cloneable
{
    String name;
    ClonePilot pilot;
    Cruiser(String name, ClonePilot pilot)
    {
        this.name = name;
        this.pilot = pilot;
    }

    //Object.clone The method is protected Modified, cannot be invoked externally. So we need to overload here. clone Method public Decorate and handle shallow copies of member variables.
    public Cruiser clone()
    {
        try
        {
            Cruiser dest = (Cruiser)super.clone();
            dest.pilot = this.pilot.clone();
            return dest;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
}
public class ClonePilot implements Serializable,Cloneable
{
    String name;
    String sex;
    ClonePilot(String name, String sex)
    {
        this.name = name;
        this.sex = sex;
    }
    //Because all member variables are primitive types, you only need to call them Object.clone()that will do
    public ClonePilot clone()
    {
        try
        {
            ClonePilot dest = (ClonePilot)super.clone();
            return dest;
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        return null;
    }
}

Here's a test

public static void main(String[] args)
{
        Cruiser cruiser = new Cruiser("VNI", new ClonePilot("Alex", "male"));
        System.out.println(cruiser);
        Cruiser cloneCruiser = cruiser.clone();
        System.out.println(cloneCruiser);
        System.out.println(cruiser.pilot);
        System.out.println(cloneCruiser.pilot);
        System.out.println(cruiser.pilot.name);
        System.out.println(cloneCruiser.pilot.name);
}

The results are as follows:

cloneObject.Cruiser@1eba861
cloneObject.Cruiser@1480cf9
cloneObject.ClonePilot@1496d9f
cloneObject.ClonePilot@3279cf
Alex
Alex

Similarly, as you can see from the console output, two different Cruiser objects refer to different Clonepilot objects. There are all data, and neither Cruiser object refers to the same member object. The deep copy is successful.

Serializable is the most common way to work, which has a small amount of code and is not easy to make mistakes. Using Cloneable mode requires a sufficient understanding of the data structure of the source object. It has a large amount of code and involves many files. Although they all need the source object type and the member object type they refer to to to implement the corresponding interface, in general, the problem is not big. But I've had the privilege of encountering a scenario where a member variable type of the source object does not implement any interfaces, and I'm not allowed to make any changes to it. I saw kryo just when my donkey in Guizhou was at a loss. Kryo is a framework of java serialization, in particular, it does not need any source object type to implement any interface, which perfectly solves my problem. In the future, I will write a guide to the use of kryo framework. Please look forward to it. (Never mumble)

Topics: Java Programming less