Citing literature Explore the clone method in Java prototype pattern
Creating objects in Java
Clone as the name implies is replication. In the Java language, the clone method is called by the object, so the object will be copied. To copy an object, you must first allocate a space of the same size as the source object, and create a new object in this space. How many ways can you create objects in the Java language?
- Using the new operator to create an object
- Copying an object using the clone method
So what are the similarities and differences between the two ways? The new operator is meant to allocate memory. When the program executes to the new operator, first look at the type after the new operator. Only when you know the type can you know how much memory space to allocate. After allocating memory, the constructor is called to fill in all fields of the object. This step is called initialization of the object. After the constructor returns, an object can be created, and its reference (address) can be published to the outside, and the reference can be used to manipulate the object. The first step of clone is similar to that of new, which is to allocate memory. When calling the clone method, the allocated memory is the same as the source object (that is, the object calling the clone method). Then, use the corresponding fields in the original object to fill in the fields of the new object. After filling, the clone method returns, and a new and identical object is created, which can also be referenced by the new object Publish to external.
Copy object or copy reference
In Java, the following similar code is very common:
Person p = new Person(23, "zhang"); Person p1 = p; System.out.println(p); System.out.println(p1);
When Person p1 = p; is a new object created? First, look at the print results:
com.pansoft.zhangjg.testclone.Person@2f9ee1ac com.pansoft.zhangjg.testclone.Person@2f9ee1ac
You can see that the printed address values are the same. Since the addresses are the same, they must be the same object. p and p1 are just references. They both point to the same object Person(23, "zhang"). This phenomenon can be called reference duplication.
After the above code is executed, the scenario in memory is as follows:
And the following code is a true clone of an object.
Person p = new Person(23, "zhang"); Person p1 = (Person) p.clone(); System.out.println(p); System.out.println(p1);
It can be seen from the printing results that the addresses of the two objects are different, that is to say, a new object is created instead of assigning the address of the original object to a new reference variable:
com.pansoft.zhangjg.testclone.Person@2f9ee1ac com.pansoft.zhangjg.testclone.Person@67f1fba0
After the above code is executed, the scenario in memory is as follows:
Deep copy or shallow copy
In the above example code, there are two member variables in Person, namely, name and age. Name is of String type and age is of int type. The code is very simple, as follows:
public class Person implements Cloneable{ private int age ; private String name; public Person(int age, String name) { this.age = age; this.name = name; } public Person() {} public int getAge() { return age; } public String getName() { return name; } @Override protected Object clone() throws CloneNotSupportedException { return (Person)super.clone(); } }
Since age is a basic data type, there is no doubt about its copy. Just copy a 4-byte integer value. But name is of String type. It is only a reference, pointing to a real String object. There are two ways to copy it: directly copy the reference value of name in the member object to the name field of the new object, or create a new String object with the same String object according to the String object pointed to by the name in the original Person object, and assign the reference of the new String to The name field of the newly copied Person object is called shallow copy and deep copy respectively. The principle of deep copy and shallow copy is shown in the following figure:
The following is verified by code. If the addresses of two Person objects are the same, it means that the names of both objects point to the same String object, that is, shallow copy. If the addresses of two objects are different, it means that they point to different String objects, that is, when copying the Person object, they copy the String object referred to by name, that is, deep copy. The verification code is as follows:
Person p = new Person(23, "zhang"); Person p1 = (Person) p.clone(); String result = p.getName() == p1.getName() ? "clone It's a shallow copy" : "clone It's a deep copy"; System.out.println(result);
The printing result is:
clone is a shallow copy
Therefore, the clone method executes a shallow copy, which should be paid attention to when writing programs.
Overwrite the clone method in Object to realize deep copy
Now in order to make a deep copy of the clone Object, we need to use the Clonable interface to override and implement the clone method. In addition to calling the clone method in the parent class to get a new Object, we also need to clone the reference variables in the class. If you only use the default clone method in the Object, which is a shallow copy, verify again with the following code:
static class Body implements Cloneable{ public Head head; public Body() {} public Body(Head head) {this.head = head;} @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } static class Head /*implements Cloneable*/{ public Face face; public Head() {} public Head(Face face){this.face = face;} } public static void main(String[] args) throws CloneNotSupportedException { Body body = new Body(new Head()); Body body1 = (Body) body.clone(); System.out.println("body == body1 : " + (body == body1) ); System.out.println("body.head == body1.head : " + (body.head == body1.head)); }
In the above code, there are two main classes, Body and Face. In the Body class, a Face object is combined. When you clone a Body object, the Face object it composes is only copied shallowly. Printing results can verify the conclusion:
body == body1 : false body.head == body1.head : true
If you want to make a deep copy of the Body object in clone, you need to make a copy of the Head object referenced by the source object in the Body's clone method.
static class Body implements Cloneable{ public Head head; public Body() {} public Body(Head head) {this.head = head;} @Override protected Object clone() throws CloneNotSupportedException { Body newBody = (Body) super.clone(); newBody.head = (Head) head.clone(); return newBody; } } static class Head implements Cloneable{ public Face face; public Head() {} public Head(Face face){this.face = face;} @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public static void main(String[] args) throws CloneNotSupportedException { Body body = new Body(new Head()); Body body1 = (Body) body.clone(); System.out.println("body == body1 : " + (body == body1) ); System.out.println("body.head == body1.head : " + (body.head == body1.head)); }
The printing result is:
body == body1 : false body.head == body1.head : false
It can be seen that the head references in body and body1 point to different head objects. That is to say, while the clone Body object, it also copies the head objects it references and makes a deep copy.
Is it really a deep copy
From the content in the previous section, we can draw the following conclusion: if you want to copy an object in depth, the object must implement the clonable interface and the clone method. In the clone method, you need to make a copy of the other objects referenced by the object, which requires that the referenced object must also implement the clonable interface and the clone sending method.
Then, according to the above conclusion, the Body class combines the Head class and the Head class combines the Face class. In order to copy the Body class in depth, you must copy the Head class in the clone method of the Body class, but when copying the Head class, the default is to copy in shallow mode, that is to say, the Face object combined in the Head will not be copied. The verification code is as follows:
static class Body implements Cloneable{ public Head head; public Body() {} public Body(Head head) {this.head = head;} @Override protected Object clone() throws CloneNotSupportedException { Body newBody = (Body) super.clone(); newBody.head = (Head) head.clone(); return newBody; } } static class Head implements Cloneable{ public Face face; public Head() {} public Head(Face face){this.face = face;} @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } static class Face{} public static void main(String[] args) throws CloneNotSupportedException { Body body = new Body(new Head(new Face())); Body body1 = (Body) body.clone(); System.out.println("body == body1 : " + (body == body1) ); System.out.println("body.head == body1.head : " + (body.head == body1.head)); System.out.println("body.head.face == body1.head.face : " + (body.head.face == body1.head.face)); }
The printing result is:
body == body1 : false body.head == body1.head : false body.head.face == body1.head.face : true
The memory structure is shown in the following figure:
So, for Body objects, is this a deep copy? In fact, it should be considered as a deep copy, because other objects referenced in the Body object (currently only the head) have been copied, that is to say, the head references in two independent Body objects have pointed to two independent head objects. However, for two head objects, they point to the same Face object, which means that the Body object is still related and not completely independent. This should be said to be an incomplete deep copy.
How to make a thorough deep copy
For the above example, how can we ensure that two Body objects are completely independent? As long as you copy the Head object, you can also copy the Face object. This requires that the Face class also implements the clonable interface, the clone method, and in the clone method of the Head object, copy the Face object it references. Modify some codes as follows:
static class Head implements Cloneable{ public Face face; public Head() {} public Head(Face face){this.face = face;} @Override protected Object clone() throws CloneNotSupportedException { //return super.clone(); Head newHead = (Head) super.clone(); newHead.face = (Face) this.face.clone(); return newHead; } } static class Face implements Cloneable{ @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
Run the above example again, and the result is as follows:
body == body1 : false body.head == body1.head : false body.head.face == body1.head.face : false
This shows that the two bodies are completely independent. The face object they refer to has been copied, that is, the independent face object has been referenced. The memory structure is as follows:
And so on. If the Face object also references other objects, such as Mouth, if it is not processed, the Body object will still be referenced to the same Mouth object through a level-1 reference after copying. In the same way, if you want the Body to be completely independent on the reference chain, you can only display the copy object.
At this point, we can draw the following conclusion: if you want to make the copied object and the source object completely independent of each other when copying an object, then each level of object in the reference chain must be displayed as a copy. So it is very troublesome to create a thorough deep copy, especially when the reference relationship is very complex, or a third-party object is referenced at a certain level of the reference chain, and this object does not implement the clone method, then all the referenced objects after it are shared. For example, if the Face class referenced by Head is a class in a third-party library and does not implement the clonable interface, then all objects after Face will be referenced by two Body objects before and after copying. Suppose that the Face object is composed of the Mouth object and the Mouth object is composed of the Tooth object. The memory structure is as follows:
Written in the end
Clone may not be used very frequently in the development of normal projects, but distinguishing between deep copy and shallow copy will give us a deeper understanding of java memory structure and operation mode. As for thorough deep copy, it is almost impossible to achieve, and the reason is explained in the previous section. Deep copy and thorough deep copy, when creating immutable objects, may have a subtle impact on the program, and may decide whether the immutable objects we create are really immutable. An important application of clone is also for the creation of immutable objects.