Concept
When duplicating objects, there are two primary aproaches. One approach involves creating a reference that points to the same memory location as the original object. In this scenario, both references point to identical data, and modifications to one will affect the other since they share the same underlying memory address. This behaves like having two pointers referencing the same object. The alternative approach involves creating an entirely new instance using the new keyword, which allocates separate memory space for the duplicate. While the content remains identical, the memory addresses differ. However, this method can be resource-intensive, especially for complex objects.
The prottoype pattern addresses these requirements. Since Object serves as the parent class for all Java objects, it provides a native clone method. However, invoking this method requires the implementing class to extend the Cloneable interface. Although Cloneable is a marker interface containing no methods, it signals that the class supports cloning operations. By implementing Cloneable and overriding the clone method, you can achieve object duplication for your custom classes.
The distinction between shallow copying and deep copying: Shallow copying creates a duplicate where changes to nested objects may affect the original object, while deep copying produces completely independent copies with no shared references.
Shallow copy implemantation:
public class VisualOrgan {
private String organName;
public VisualOrgan(String organName){
this.organName = organName;
}
public String getOrganName() {
return organName;
}
public void setOrganName(String organName) {
this.organName = organName;
}
@Override
public String toString() {
return "VisualOrgan{" +
"organName='" + organName + '\'' +
'}';
}
}
import java.io.Serializable;
public class HumanEntity implements Cloneable, Serializable {
private static final long serialVersionUID = -2050795770781171788L;
private String fullName;
private String residence;
VisualOrgan visualOrgan;
public HumanEntity(){}
public HumanEntity(VisualOrgan visualOrgan){
this.visualOrgan = visualOrgan;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getResidence() {
return residence;
}
public void setResidence(String residence) {
this.residence = residence;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (HumanEntity) super.clone();
}
@Override
public String toString() {
return "HumanEntity{" +
"fullName='" + fullName + '\'' +
", residence='" + residence + '\'' +
", visualOrgan=" + visualOrgan +
'}';
}
}
public class Application {
public static void main(String[] args) throws CloneNotSupportedException {
HumanEntity entity = new HumanEntity(new VisualOrgan("originalVisualOrgan"));
entity.setFullName("originalEntity");
entity.setResidence("originalAddress");
HumanEntity clonedEntity = (HumanEntity) entity.clone();
clonedEntity.setFullName("clonedName");
clonedEntity.setResidence("clonedAddress");
clonedEntity.visualOrgan.setOrganName("modifiedVisualOrgan");
System.out.println(entity.toString());
System.out.println(clonedEntity.toString());
System.out.println(entity == clonedEntity);
System.out.println(entity.equals(clonedEntity));
System.out.println(entity.getClass().equals(clonedEntity.getClass()));
System.out.println(entity.getClass() == clonedEntity.getClass());
}
}
Results:
HumanEntity{fullName='originalEntity', residence='originalAddress', visualOrgan=VisualOrgan{organName='modifiedVisualOrgan'}}
HumanEntity{fullName='clonedName', residence='clonedAddress', visualOrgan=VisualOrgan{organName='modifiedVisualOrgan'}}
false
false
true
true
Deep copying: Creates completely independent copies with no shared relationships to the original
Add a deep cloning method to the HumanEntity class:
public Object performDeepClone() throws IOException, ClassNotFoundException {
/* Serialize current object to binary stream */
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectStream = new ObjectOutputStream(outputStream);
objectStream.writeObject(this);
/* Deserialize from binary stream to create new object */
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return objectInputStream.readObject();
}
Update the main method:
import java.io.IOException;
public class Application {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
HumanEntity entity = new HumanEntity(new VisualOrgan("originalVisualOrgan"));
entity.setFullName("originalEntity");
entity.setResidence("originalAddress");
//HumanEntity clonedEntity = (HumanEntity) entity.clone();
HumanEntity clonedEntity = (HumanEntity) entity.performDeepClone();
clonedEntity.setFullName("clonedName");
clonedEntity.setResidence("clonedAddress");
clonedEntity.visualOrgan.setOrganName("modifiedVisualOrgan");
System.out.println(entity.toString());
System.out.println(clonedEntity.toString());
System.out.println(entity == clonedEntity);
System.out.println(entity.equals(clonedEntity));
System.out.println(entity.getClass().equals(clonedEntity.getClass()));
System.out.println(entity.getClass() == clonedEntity.getClass());
}
}
Results:
HumanEntity{fullName='originalEntity', residence='originalAddress', visualOrgan=VisualOrgan{organName='originalVisualOrgan'}}
HumanEntity{fullName='clonedName', residence='clonedAddress', visualOrgan=VisualOrgan{organName='modifiedVisualOrgan'}}
false
false
true
true
This demonstrates the prototype design pattern implementation. The cloning mechanism relies on the native Object class methods, which often appears as an interview question regarding which methods from the base Object class have been utilized.