This article introduces the concept of shallow clone and deep clone in Java in detail, and the case demonstrates how to achieve deep clone!

1 Cloning Overview

There are two types of object cloning in Java: one is shallow cloning, the other is deep cloning. First Java Clone method for object cloning mechanism is: Member variables of the basic data type of the object are all copied. Member variables of the reference type are not copied. Only the reference to that variable is copied. A change to a member variable of the reference type of one object affects a member variable of the reference type of the other cloned object or source object.

Shallow cloning: The most common type of cloning is that the object implements the cloneable interface and overwrites the Clone method. Then the clone method is called once to clone an object. If the source object contains a member variable of the reference type, then the clone is said to be shallow. The reference of two objects refers to the same memory address, that is, the same object.

Deep cloning: Both the basic data type variable and the reference type variable point to the object will be copied, that is, for the reference type member variable is really copied, open up a new space to save, so that the two reference type attributes do not affect each other.

2 Deep clone implementation

There are three ways to achieve deep cloning:

  1. Overwrite the clone method to nest the clone inside

    1. The principle of this method is in need of cloning object and the object of the class all of the variables of reference types implement cloneable interface, otherwise throw CloneNotSupportedException will be a reference type variable is a clone. The actual operation is to override the clone method of the source object and nest the clone method inside it.
    2. First let the source object call cloning method to obtain the cloned object, and then get member variable object of the type of the object being cloned, call cloning method for the member variable object, at this time a member variable object is cloned, the last member variables of the cloned object, set to clone the object’s new member variables, return the cloned object, Deep cloning can be realized.
  2. Using a serialized stream

    1. The Serializable interface is implemented for both the object to be serialized and the class of the object’s reference type member variable, and the object is serialized to the output stream, and then it is deserialized to the object. The deserialization of the object is similar to running the constructor of the new object. In general, serializing to an external file, you only need to clone the object, not the external file, so our serialization and deserialization should also work best in memory, so we also use streams that manipulate data in memory, ByteArrayOutputStream and ByteArrayInputStream. By default, both the output and the read operate in an array in memory.
    2. Create a serialized ObjectOutputStream and pass it in. Write the serialized object into the internal array using the writeObject method of the serialized stream. Then create a ByteArrayInputStream memory array to read the stream, passing in an array to read the data, which is obtained by the toByteArray method of the memory array output stream. The data in the array is actually the objects that have been serialized into binary data. Finally, create an ObjectInputStream to deserialize the stream, pass in an in-memory array to read the stream, and use the deserialization stream’s readObject method to deserialize the information of the objects in the array. The deserialized object is a new object, completing the deep clone.
    3. You can also fix the version number of the object to be serialized, and define a private static final Long serialVersionUID, but note that static and transient members cannot be serialized.
  3. Use open source utility classes

    1. Examples include the Json utility class (first converted to A Json string and then to an object), Spring’s BeanUtils (easy to use in Spring projects), Cglib’s BeanCopier (fastest), and Apache’s BeanUtils (slow, use with care!).

3 cases

For convenience, the get and set methods are not used here.

3.1 Testing the common Clone method — shallow clone

public class Teacher implements Cloneable { private String name; private int age; private Student stu; Public static void main (String [] args) throws CloneNotSupportedException {Student stu = new Student (" bill ", 24); Teacher tea = new Teacher(" zhang SAN ", 30, stu); Teacher teaClone = (Teacher) tea.clone(); Stu. Name =" name "; // Change the data of tea tea. Name =" zhang sanchang "; / / the cloned data of inner class stu has also been affected, not rewrite the clone method of implementation is shallow clone, tea object type attribute stu is refers to the same object System. Out. The println (teaClone); System.out.println(tea); } public Teacher(String name, int age, Student stu) { super(); this.name = name; this.age = age; this.stu = stu; } public static class Student implements Cloneable { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + ''' + ", age=" + age + '}'; } } @Override public String toString() { return "Teacher{" + "name='" + name + ''' + ", age=" + age + ", stu=" + stu + '}'; }}Copy the code

3.2 Use the overwritten Clone method — deep clone

First, override the clone method for both the inner and outer classes, and then override the Clone method:

public class Teacher implements Cloneable { private String name; private int age; private Student stu; Public static void main (String [] args) throws CloneNotSupportedException {Student stu = new Student (" bill ", 24); Teacher tea = new Teacher(" zhang SAN ", 30, stu); Teacher teaClone = (Teacher) tea-.clone (); Stu. Name =" name "; // Change the data of tea tea. Name =" zhang sanchang "; / / the cloned data of inner class stu data is not affected, rewrite the clone method, implementation is a deep clone, object type attribute of the tea stu is refers to the different object System. Out. Println (teaClone); System.out.println(tea); } @ Override protected Object clone () throws CloneNotSupportedException {/ / rewrite the clone method the Teacher tea = (the Teacher) super.clone(); Stu = ((Student) tea.stu.clone()); return tea; } public Teacher(String name, int age, Student stu) { super(); this.name = name; this.age = age; this.stu = stu; } public static class Student implements Cloneable { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + ''' + ", age=" + age + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } @Override public String toString() { return "Teacher{" + "name='" + name + ''' + ", age=" + age + ", stu=" + stu + '}'; }}Copy the code

This method does not work for classes that cannot be overridden, such as arrays!

3.3 Using serialized streams — deep cloning

With both classes implementing the Serialzable interface, the Clone method can be discarded and the serialized stream can be used.

public class Teacher implements Serializable { private String name; private int age; private Student stu; Public static void main(String[] args) throws IOException, ClassNotFoundException {Student stu = new Student(" "), 24); Teacher tea = new Teacher(" zhang SAN ", 30, stu); ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); // Write the data tea to the serialized stream, which is then passed to the in-memory array output stream, serializing the object as oos. WriteObject (tea) of type byte[]; ByteArrayInputStream bai = new ByteArrayInputStream(bao.tobyteArray ()); ObjectInputStream ois = new ObjectInputStream(bai); // Use readObject to read data from the deserialized stream and deserialize data of type byte[] into a Teacher object Teacher teaClone = (Teacher) ois.readObject(); Stu. Name = "li si change "; // Change the data of tea. Name = "three "; / / the cloned data of inner class stu data is not affected, rewritten clone method, realized the deep cloning System. Out. The println (teaClone); System.out.println(tea); } public Teacher(String name, int age, Student stu) { super(); this.name = name; this.age = age; this.stu = stu; } public static class Student implements Serializable { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + ''' + ", age=" + age + '}'; } } @Override public String toString() { return "Teacher{" + "name='" + name + ''' + ", age=" + age + ", stu=" + stu + '}'; }}Copy the code

3.4 Using open Source Tools

Here only introduces the use of Json tool: Gson.

/ / maven rely on <! -- https://mvnrepository.com/artifact/com.google.code.gson/gson --> <dependency> <groupId>com.google.code.gson</groupId> < artifactId > gson < / artifactId > < version > 2.8.5 < / version > < / dependency >Copy the code

You can also use the form of jar packages to introduce the Gson tool: Gson JAR downloads

public class Teacher { private String name; private int age; private Student stu; Public static void main(String[] args) {Student stu = new Student(" li si ", 24); Teacher tea = new Teacher(" zhang SAN ", 30, stu); /* use the Gson tool */ Gson Gson = new Gson(); String teaStr = gson.tojson (tea); String teaStr = gson.tojson (tea); Teacher GsonTea = gson.fromjson (teaStr, teacher.class); Stu. Name = "name "; // Change the data of tea tea. Name = "zhang sanchang "; Println (GsonTea); / / System.out.println(GsonTea); / / System.out.println(GsonTea); / / System.out.println(GsonTea); / / System.out.println(GsonTea); System.out.println(tea); } public Teacher(String name, int age, Student stu) { super(); this.name = name; this.age = age; this.stu = stu; } public static class Student { private String name; private int age; public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + ''' + ", age=" + age + '}'; } } @Override public String toString() { return "Teacher{" + "name='" + name + ''' + ", age=" + age + ", stu=" + stu + '}'; }}Copy the code

As a result, we did not implement the Cloneable and Serializable interfaces, but it did not affect the result.