Object cloning in Java
Cloneable, shallow vs deep copy, prefer copy constructors.
How clone() works
To make a class cloneable: implement the **Cloneable** marker and override clone() — usually public, with your own class as return type — calling **super.clone()**. Object.clone() then makes a field-by-field copy.
class Bag implements Cloneable {
List<String> items = new ArrayList<>();
public Bag clone()
throws CloneNotSupportedException {
return (Bag) super.clone();
}
}Copy of a grid
Arrays have clone() too, and it works the same way. What prints?
int[][] grid = {{1, 2}, {3, 4}};
int[][] copy = grid.clone();
copy[0][0] = 99;
System.out.println(grid[0][0]);199Compile error
Show the answer
clone() copied the outer array — that is, the references to the rows. Both grids share the same row arrays, so the change shows up in the original. A cloned Bag shares its items list exactly the same way.
Shallow vs deep
Shallow copy: each field's value is copied — for a reference, that's just the address, so both objects share the referenced object. Deep copy: you also copy the mutable things you reference, so the two are fully independent.
public Doc clone()
throws CloneNotSupportedException {
Doc copy = (Doc) super.clone();
copy.pages = pages.clone(); // deep
return copy;
}Re-sharing instead of copying
copy.pages = pages; looks like copying but just re-shares the same array. Editing copy.pages would still change the original. The fix is copy.pages = pages.clone();.
Doc copy = (Doc) super.clone();
copy.pages = pages; // still shared!Clone vs copy constructor
class Bag implements Cloneable {
public Bag clone()
throws CloneNotSupportedException {
return (Bag) super.clone();
}
}Needs a marker, a cast and a checked exception — and it's still shallow.
class Bag {
List<String> items = new ArrayList<>();
Bag() { }
Bag(Bag o) {
items = new ArrayList<>(o.items);
}
}Plain Java: new Bag(a) gets its own list, so the two bags are independent.
Why experts avoid clone()
Effective Java recommends copy constructors or factories over clone(). Why?
Think about it, then reveal the answer
clone() skips constructors, needs casts and a checked exception, and clashes with final fields (you can't reassign them in the copy). A copy constructor runs normal initialization and can set final fields.
Defensive copies
Shared mutable state causes real bugs: a cached list that a caller modifies, or a Date handed out by a getter and changed outside. Professionals copy mutable data on the way in and out — or avoid the problem entirely with immutable types like records and List.copyOf.
Key takeaways
- Implement Cloneable and override clone() (public, covariant return)
- super.clone() copies fields shallowly
- Deep copy: also copy the mutable objects you reference
- Prefer copy constructors: new Order(other)
💡 Photocopying an address book copies the addresses, not the houses.
Arrays override clone() as public and return the exact array type, so int[] b = a.clone(); needs no cast and no try/catch — the friendliest clone in Java.
Practice questions
What does this print?
class Bag implements Cloneable {
List<String> items = new ArrayList<>();
public Bag clone() throws CloneNotSupportedException {
return (Bag) super.clone();
}
}
void main() throws Exception {
Bag a = new Bag(), b = a.clone();
b.items.add("pen");
System.out.println(a.items);
}- [pen]
- []
- Throws CloneNotSupportedException
- null
Check your answer
[pen]. The clone got a copy of the reference, not of the list. Adding through b changes the list a also uses.
What does this print?
class Bag {
List<String> items;
Bag() { items = new ArrayList<>(); }
Bag(Bag o) { items = new ArrayList<>(o.items); }
}
void main() {
Bag a = new Bag(), b = new Bag(a);
b.items.add("pen");
System.out.println(a.items.size() + " " + b.items.size());
}- 0 1
- 1 1
- 0 0
- 1 0
Check your answer
0 1. The copy constructor builds a new list, so the two Bags are independent: a still has 0 items, b has 1.