Learning outcome: Distinguish reference assignment, shallow copying, and application-defined deep copying, and implement copies that preserve the intended independence of mutable state.
Suppose a student record is copied before its marks are edited. Whether the original changes depends on what was copied: only the reference, the outer Student object, or the mutable objects reachable from it. Copying is therefore a question of object identity and sharing, not merely memory usage.
Assignment Is Not an Object Copy
The statement Student second = first; copies a reference value. Both variables refer to the same student. A change to that student's fields is visible through either variable. No new student object is created.
| Operation | Outer object | Nested mutable objects |
|---|---|---|
| Reference assignment | Shared | Shared |
| Shallow copy | New | References are copied, so nested objects remain shared |
| Deep copy | New | Required mutable objects are copied too |
A deep copy is an application-defined design decision, not a special Java keyword or universal library operation. It should preserve the original values while creating independence for the mutable state that must not be shared. Merely allocating an empty nested object loses data. Immutable values, such as strings, can usually be shared safely.
Cloneable and Object.clone
Object.clone() is protected and, according to the Object contract, creates a field-by-field shallow copy: primitive values are copied and reference values are copied without recursively cloning the referenced objects. The object must implement the marker interface Cloneable for super.clone() to succeed; otherwise CloneNotSupportedException is thrown. Cloneable itself declares no clone() method. A class that chooses this mechanism commonly exposes a public, type-specific clone(). See the Object.clone API contract.
The clone operation allocates a distinct outer object but does not call that object's constructor. Because reference fields are copied as references, nested mutable objects remain shared unless the class explicitly copies them afterwards.
Compare All Three Behaviors
Save as CloningDemo.java:
import java.util.Arrays;
public class CloningDemo {
static class Student implements Cloneable {
String name;
int[] marks;
Student(String name, int[] marks) {
this.name = name;
this.marks = marks;
}
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
Student deepCopy() throws CloneNotSupportedException {
Student copy = clone();
copy.marks = marks.clone();
return copy;
}
}
public static void main(String[] args)
throws CloneNotSupportedException {
Student original = new Student("Asha", new int[] {70, 80});
Student alias = original;
Student shallow = original.clone();
Student deep = original.deepCopy();
alias.name = "Asha K";
shallow.marks[0] = 99;
deep.marks[1] = 95;
System.out.println("Alias is original: " + (alias == original));
System.out.println("Shallow is original: " + (shallow == original));
System.out.println("Original: " + original.name + " "
+ Arrays.toString(original.marks));
System.out.println("Shallow: " + shallow.name + " "
+ Arrays.toString(shallow.marks));
System.out.println("Deep: " + deep.name + " "
+ Arrays.toString(deep.marks));
}
}
Output:
Alias is original: true
Shallow is original: false
Original: Asha K [99, 80]
Shallow: Asha [99, 80]
Deep: Asha [70, 95]
alias.name changes the original object because alias and original are two references to the same Student. The shallow copy is a different Student, so its name field still refers to the earlier immutable string. However, both the original and shallow copy refer to the same marks array, so changing an array element is visible through both. The deep copy owns a separate array initialized with the same original values.
In simple terms: A shallow copy makes another student folder but keeps the same marks sheet inside it. A deep copy also duplicates the marks sheet, so edits can be independent.
Copy Constructors as an Alternative
A copy constructor states the copying rules explicitly and avoids the special contract of Cloneable. For many domain classes this is easier to read and maintain because the code shows exactly which fields are shared and which are duplicated. The following constructor fragment could be placed inside the Student class:
Student(Student other) {
this.name = other.name;
this.marks = other.marks.clone();
}
Then new Student(original) creates a separate record and marks array. This class assumes its marks array is non-null. A general-purpose class should define how missing arrays are handled and whether its ordinary constructor also defensively copies caller-owned arrays.
Cloning an int[] copies its primitive elements, which is sufficient here. Cloning an array of mutable Address objects copies only the array structure; the Address instances remain shared. Likewise, new ArrayList<>(existing) creates another list containing the same element references. A true graph copy involving cycles or intentionally shared nodes needs deliberate identity tracking so that the copy neither recurses forever nor accidentally destroys intended sharing.
Cloning and Serialization Compared
| Question | Cloning or explicit copying | Serialization |
|---|---|---|
| Main purpose | Duplicate data within a program | Encode state for storage or transfer |
| Output | Another object | Bytes |
| Main mechanism | clone, constructor, or factory method |
Object streams and Serializable |
| Nested objects | Depends on copying implementation | Saved graph follows serialization rules |
Serialization can technically be used to produce another object graph, but it introduces byte-stream encoding, serializability constraints, version compatibility, and deserialization concerns. When the goal is simply an understandable in-memory duplicate, prefer an explicit copy constructor, factory method, or carefully implemented cloning operation.
Practice
- Predict the output if the deep copy is created after changing
shallow.marks[0]. - Add a mutable address field and implement a copy that preserves its values without sharing the address.
- Replace
Cloneablewith a copy constructor and reproduce the same independent marks behavior.
Quick Check
1. Does implementing Cloneable automatically make clone public?
No. The marker interface declares no method. The class must expose a suitable copy operation itself.
2. Must a deep copy duplicate every String?
No. Strings are immutable and can safely be shared. Independence is needed for mutable state that should not remain shared.
3. Is new ArrayList<>(oldList) a deep copy of student objects?
No. It creates another list containing the same student references.
Summary
Reference assignment shares the same object. A shallow copy creates a new outer object while retaining references to nested objects. A deep copy is defined by the independence your application requires and must duplicate the relevant mutable state deliberately. Continue with lambda expressions and functional interfaces.