23 · 32 · 5 min
How deep does the copy go
Copying an object that contains other objects raises a question with no universal answer: are the contents copied too?
Shallow
Fields are copied. Objects those fields point at are shared.
class Order {
String id;
List<Item> items;
}
Order copy = new Order(original.id, original.items); // same list
copy.items.add(new Item("extra"));
// original.items now has the extra item tooCheap, and the two objects are entangled.
Deep
Everything reachable is duplicated.
Order copy = new Order(original.id, new ArrayList<>(original.items));That is one level deeper. If Item itself holds mutable state, this is still shallow with respect to the items, and truly deep copying means recursing all the way down.
Where it hurts
The classic is a defensive copy that is not deep enough:
public List<Item> getItems() {
return new ArrayList<>(items); // caller cannot add or remove
}The caller cannot change the list, and can still call items.get(0).setPrice(0) and modify the order. A copy of a list of mutable objects protects the list and not the objects.
public List<Item> getItems() {
return List.copyOf(items); // still shares the Items
}The only real fix is for Item to be immutable, which is why the next page follows this one.
Circular references
A deep copy of a graph with a cycle recurses forever unless it tracks what it has already copied. Serialisation based copying handles this, which is one reason people reach for it.
import copy
copy.copy(x) # shallow
copy.deepcopy(x) # deep, and handles cyclesThe costs
| Shallow | Deep | |
|---|---|---|
| Speed | fast | proportional to the whole graph |
| Memory | small | duplicates everything |
| Independent | no | yes |
| Cycles | fine | needs tracking |
Deep copying a large object graph on every request is a real performance problem, and it is usually a sign that the design is fighting mutability.
The alternative
Do not copy. If the object cannot change, sharing it is safe and free, and the whole question disappears. That is the argument for immutability, and it is why functional languages have no shallow or deep distinction to teach.