Object Cloning in Java
Navika has a class timetable, and Srihitha wants one just like it, then plans to change a period or two. Writing Timetable copy = original; doesn't copy anything: both variables would point at the same object. Java's built-in way to duplicate an object is clone(), inherited from Object. It's quick to use, but its default copy is shallow, which leads to one of the classic Java bugs.
Shallow Copy, Then Deep Copy
import java.util.Arrays;
public class Cloning {
static class Timetable implements Cloneable {
String owner;
String[] periods;
Timetable(String owner, String[] periods) {
this.owner = owner;
this.periods = periods;
}
@Override
public Timetable clone() {
try {
return (Timetable) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
Timetable deepCopy() {
Timetable copy = clone();
copy.periods = periods.clone();
return copy;
}
}
static void print(Timetable t) {
System.out.println(t.owner + " " + Arrays.toString(t.periods));
}
public static void main(String[] args) {
Timetable navika = new Timetable("Navika",
new String[] {"Math", "Physics", "English"});
Timetable shallow = navika.clone();
shallow.owner = "Srihitha";
shallow.periods[0] = "Chemistry";
print(navika);
print(shallow);
Timetable deep = navika.deepCopy();
deep.owner = "Advitha";
deep.periods[1] = "Biology";
print(navika);
print(deep);
}
}Navika [Chemistry, Physics, English]
Srihitha [Chemistry, Physics, English]
Navika [Chemistry, Physics, English]
Advitha [Chemistry, Biology, English]How it works
implements Cloneableswitches onObject.clone()for this class. Without it,super.clone()throwsCloneNotSupportedException(see "Marker Interfaces in Java").- The override is
public(the original isprotected) and returnsTimetable(a covariant return), so callers don't need a cast. super.clone()creates a new object and copies each field as is.owneris a reference to an immutableString, so changing it on the copy is harmless. Butperiodsis a reference to an array, and both objects now share that one array.- So
shallow.periods[0] = "Chemistry"changes Navika's timetable too. That's the shallow-copy bug. deepCopy()also copies the array (periods.clone()), so the change to Advitha's copy doesn't leak back into Navika's timetable.
Shallow vs Deep
| Shallow copy | Deep copy | |
|---|---|---|
| New outer object | Yes | Yes |
| Primitive fields | Copied | Copied |
References to immutable objects (String, Integer) | Shared, which is safe | Shared, which is safe |
References to mutable objects (arrays, lists, Date) | Shared: changes leak between copies | Copied: fully independent |
| Cost | Cheap | More work and memory |
Why Many Developers Avoid clone()
clone() has a long list of quirks: Cloneable has no clone method of its own, Object.clone is protected, it throws a checked exception you must catch, it bypasses constructors, and it doesn't work with final fields that need new values. Simpler alternatives:
| Alternative | Example |
|---|---|
| Copy constructor | new Timetable(original) (see "Copy Constructor") |
| Static copy factory | Timetable.copyOf(original) |
| Immutable objects | Nothing to copy: share freely (see "How to Create an Immutable Class in Java") |
| Collection copies | new ArrayList<>(list), List.copyOf(list), array.clone() |
Common Mistakes
Assuming clone() is deep. Any mutable field needs its own copy.
Forgetting implements Cloneable. The code compiles, then super.clone() throws at runtime.
Leaving clone() protected. Outside code can't call it; override it as public.
Interview Corner
What is the difference between shallow and deep copy?
A shallow copy creates a new object and copies field values, so referenced objects are shared between the original and the copy. A deep copy also creates copies of the referenced mutable objects, recursively, so the two are completely independent. Object.clone() performs a shallow copy.
Why is Cloneable called a broken interface?
It's a marker with no clone method, so implementing it doesn't make clone public or callable. Object.clone is protected, throws a checked CloneNotSupportedException, creates objects without calling constructors, and is shallow. Copy constructors or static factories are usually clearer and safer.
Practice
- Easy: Clone an
int[]withclone(), change the copy, and show the original is unchanged. - Harder: Give
TimetableaList<String> notesfield. UpdatedeepCopy()so the notes list is copied too, and prove it with a test inmain.
Key Takeaways
Was this tutorial helpful?