This is the second day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021
This article is excerpted from This is How Design Patterns should Be Learned
1 Analyze the problems caused by shallow clone API of JDK
In the API provided by Java, there is no need to manually create the abstract stereotype interface, because Java already has the Cloneable abstract stereotype interface built into it. Custom types simply implement this interface and override the object.clone () method to copy the class. A look at the JDK source code shows that Cloneable is an empty interface. Java provides the Cloneable interface only to inform the Java virtual machine at run time that it is safe to use the clone() method on the class. And if the class does not implement the Cloneable interface, then call clone () method will throw CloneNotSupportedException anomalies. In general, if the Clone () method is used, the following conditions must be met.
(1) For any object o, there is o.lone ()! = o. In other words, the clone object is not the same object as the prototype object.
(2) o.lone ().getClass() == o.getclass (). In other words, the copied object is of the same type as the original object.
(3) If the equals() method of o is properly defined, then o.lone ().equals(o) should be established.
We should follow these three conditions when designing the Clone () method of our custom class. In general, the first two of these three conditions are required and the third is optional. The following uses the API application provided by Java to implement the prototype pattern. The code is as follows.
class Client {
public static void main(String[] args) {
// Create a prototype object
ConcretePrototype type = new ConcretePrototype("original");
System.out.println(type);
// Copy the prototype object
ConcretePrototype cloneType = type.clone();
cloneType.desc = "clone";
System.out.println(cloneType);
}
static class ConcretePrototype implements Cloneable {
private String desc;
public ConcretePrototype(String desc) {
this.desc = desc;
}
@Override
protected ConcretePrototype clone(a) {
ConcretePrototype cloneType = null;
try {
cloneType = (ConcretePrototype) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloneType;
}
@Override
public String toString(a) {
return "ConcretePrototype{" +
"desc='" + desc + '\' ' +
'} '; }}}Copy the code
The super.clone() method is efficient because it copies a block of memory directly from the heap as a binary stream. Because the super.clone() method is based on in-memory replication, the object’s constructor is not called, that is, it does not undergo initialization. In everyday development, using the super.clone() method does not meet all requirements. If a reference object attribute exists in a class, that attribute of the prototype and clone points to a reference to the same object.
@Data
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone(a) {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null; }}@Override
public String toString(a) {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\' ' +
", hobbies=" + hobbies +
'} '; }}Copy the code
Modify the client test code.
public static void main(String[] args) {
// Create a prototype object
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<String>();
hobbies.add("Calligraphy");
hobbies.add("Fine arts");
prototype.setHobbies(hobbies);
System.out.println(prototype);
// Copy the prototype object
ConcretePrototype cloneType = prototype.clone();
cloneType.getHobbies().add("Technology control");
System.out.println("Prototype object:" + prototype);
System.out.println("Clone object:" + cloneType);
}
Copy the code
When we added a property called hobbies to the copied object, we found that the prototype object also changed, which was obviously not what we expected. Because the object we want to copy should be separate from the prototype object, no longer related. From the test results, it looks like hobbies share a memory address, meaning instead of copying values, you copy the referenced address. That way, if we change the property values in any of the objects, the protoType and cloneType hobbies values will change. This is what we often call a shallow clone. Only a full copy of the value type data, no assignment reference object. In other words, all reference objects still refer to the original object, which is obviously not what we want. So how do you solve this problem? Java’s native clone() method does shallow cloning. If we want to deep-clone, we can manually allocate another block of memory to the related properties of the copied object after super.clone(), but manual allocation can be cumbersome if the prototype object maintains many reference properties. Therefore, in Java, if you want to do a deep clone of a prototype object, you usually use Serializable.
2 Use serialization to implement deep cloning
A deepClone() method is added to build on the previous section.
/** * Created by Tom. */
@Data
public class ConcretePrototype implements Cloneable.Serializable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone(a) {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null; }}public ConcretePrototype deepClone(a){
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ConcretePrototype)ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null; }}@Override
public String toString(a) {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\' ' +
", hobbies=" + hobbies +
'} '; }}Copy the code
The client call code is as follows.
public static void main(String[] args) {
// Create a prototype object
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<String>();
hobbies.add("Calligraphy");
hobbies.add("Fine arts");
prototype.setHobbies(hobbies);
// Copy the prototype object
ConcretePrototype cloneType = prototype.deepCloneHobbies();
cloneType.getHobbies().add("Technology control");
System.out.println("Prototype object:" + prototype);
System.out.println("Clone object:" + cloneType);
System.out.println(prototype == cloneType);
System.out.println("Hobby of prototype object:" + prototype.getHobbies());
System.out.println("Clone object's hobby:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());
}
Copy the code
Run the program and get the result shown in the figure below, which is consistent with the expected result.
From the run results, we did complete the deep clone.
Pay attention to “Tom play architecture” reply to “design pattern” can obtain the complete source code.
Tom play architecture: 30 real cases of design patterns (attached source code), the challenge of annual salary 60W is not a dream
This article is “Tom play structure” original, reproduced please indicate the source. Technology is to share, I share my happiness! If this article is helpful to you, welcome to follow and like; If you have any suggestions can also leave a comment or private letter, your support is my motivation to adhere to the creation. Pay attention to “Tom bomb architecture” for more technical dry goods!