Introduction
Copying a Java object means creating a new class instance with the same state as the original object. The state can be replicated in two different ways: by making a shallow copy or a deep copy. In this article, we will discuss the meaning and implications of this.
Difference Between Primitive and Reference Types in the Copy Operation
A Java class can contain both primitive and reference variables as attributes. A variable of primitive type contains an actual raw value, set to a default if not explicitly assigned, and stored in Java Stack memory. A variable of a reference type, on the other hand, can contain a reference to an object stored in Heap memory or a null value. We can also call a reference-type attribute of a class a dependency of that class.
If we copy the value of a primitive variable A into a separate variable B, B will have its own copy of the value. So, if we change the value in B, A will retain its original value.
If A and B are of a reference type, after the copy, B will contain a reference to the same object referenced by A.
2.1. Shallow Copy
In a shallow copy, we copy the primitive attributes of the object plus its reference-type ones, i.e., its dependencies. In this scenario, the state of the correspondent dependencies is shared between the original and copied objects (they both point to the same dependency object).
2.2. Deep Copy
In a deep copy, we make a shallow copy of the object plus a shallow copy of each dependency (and of the dependencies of each dependency if needed). In this case, the correspondent dependencies in the original object and the copied instance will have their own independent states.
Shallow and Deep Copy Implementation
To implement a shallow copy, we can override the protected clone method of the Object class by making it public. The Object‘s clone method performs a shallow copy. It’s also necessary to implement the Cloneable marker interface to avoid a CloneNotSupportedException.
For a deep copy, we must repeat the clone operation for the dependencies using a specific method (deepClone in our example). To simplify the example, we consider a class Main with a single attribute called Dependency:
public class Main implements Cloneable {
private Dependency dependency;
public Main(Dependency dependency) {
super();
this.dependency = dependency;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Object deepClone() throws CloneNotSupportedException {
Main dependentClone = (Main) super.clone();
Dependency dependencyClone = (Dependency) this.dependency.clone();
dependentClone.setDependency(dependencyClone);
return dependentClone;
}
public Dependency getDependency() {
return dependency;
}
public void setDependency(Dependency dependency) {
this.dependency = dependency;
}
}
public class Dependency implements Cloneable {
private int value = 13;
public Dependency(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
We can use unit tests to verify the behavior in the two scenarios:
@Test
public void givenAnObjectAndADependency_whenShallowClonedAndDepencyChanges_ThenDepencyHasNewValue() throws Exception {
Dependency dependency = new Dependency(13);
Main main = new Main(dependency);
Main shallowClone = (Main) main.clone();
dependency.setValue(17);
assertEquals(17, shallowClone.getDependency()
.getValue());
}
@Test
public void givenAnObjectAndADependency_whenDeepClonedAndDepencyChanges_ThenDepencyKeepsOldValue() throws Exception {
Dependency dependency = new Dependency(13);
Main main = new Main(dependency);
Main deepClone = (Main) main.deepClone();
dependency.setValue(17);
assertEquals(13, deepClone.getDependency()
.getValue());
}
In the shallow copy scenario, the dependency value of the copied object contains the last set value, which is 17. In the deep copy, the 13 value is returned instead. This is because the dependency has been copied as well, and its state with it.
Conclusion
In this article, we have seen how the state of the objects is handled in a shallow or deep copy scenario. The example code is available on GitHub.
