skip to content

How would you implement a defensive copy of a class instead of using clone(), and why is the copy-constructor approach preferable?

level: middleimportance: should knowfreq 52%

answer

  1. Constructor takes same-type arg, copies fields
  2. Deep-copy mutable fields, share immutable ones
  3. Runs constructor → validation/invariants hold
  4. No checked exception, no cast, works with final
  5. Interface-typed arg → convert representation (new ArrayList<>(coll))

basics

~20 s

Write a constructor that takes another instance and copies its fields, deep-copying any mutable ones: new Point(Point p) { this.x = p.x; this.y = p.y; }. It runs normal construction (so validation applies), needs no casts or checked exceptions, and works with final fields — unlike clone().

solid answer

~40 s

A copy constructor is just a constructor that takes an instance of the same type and copies its state, deep-copying mutable fields so the new object is independent. A copy factory is the static-method equivalent. They're preferable to clone() because they go through normal construction, so all the class's invariants and validation still run; they don't depend on the fragile Cloneable contract; they don't throw a checked CloneNotSupportedException or require a cast; they work fine with final fields, which clone() can conflict with; and they can take an interface-typed argument, letting you convert representations (e.g. copy any Collection into an ArrayList). The JDK uses this pattern everywhere — ArrayList, HashSet, and TreeMap all have copy constructors. The main thing to remember is to deep-copy mutable members so you don't accidentally share state.

go deeper

for a junior

Can write a basic copy constructor that copies primitive/String fields and knows it's the alternative to clone().

for a middle

Deep-copies mutable fields correctly, knows construction runs validation, and lists the practical advantages over clone().

for a senior

Explains conversion constructors with interface-typed args, defensive copying on input and output, and the immutable-field optimization; ties it to the Cloneable flaws.

for a principal

Sets copy/immutability conventions for an API, balancing allocation cost of defensive copies against safety, and chooses immutable types to avoid copying altogether.

## What a defensive / independent copy means A **copy** of an object is a second object with the same logical state but **independent** lifetime — changing one must not affect the other. "Defensive copy" emphasizes the motivation: you copy a mutable object you received or are about to expose so external code can't mutate your internal state behind your back. ## The copy-constructor recipe A **copy constructor** is a constructor whose single parameter is an instance of the same class: ```java public final class Period { private final Date start; // Date is mutable (legacy) private final Date end; public Period(Date start, Date end) { // defensive copy of inputs, then validate the copies this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); if (this.start.after(this.end)) throw new IllegalArgumentException("start after end"); } // copy constructor public Period(Period other) { this.start = new Date(other.start.getTime()); // deep copy this.end = new Date(other.end.getTime()); } public Date start() { return new Date(start.getTime()); } // defensive copy out } ``` Key points: (1) you copy each field; (2) for a **mutable** field you create a *new* instance (deep copy) rather than copying the reference (which would share state); (3) because it's an ordinary constructor, any validation logic runs, so you can't produce an invalid copy. The **copy factory** is the static form of the same idea: ```java public static Period copyOf(Period other) { return new Period(other); } ``` A factory can have a descriptive name, can return a cached instance, and can return a subtype — the usual static-factory advantages. ## Why this beats clone() | Concern | clone() | copy constructor / factory | |---|---|---| | Runs constructors/invariants | No — bypasses them | Yes — normal construction | | Checked exception | Throws CloneNotSupportedException | None | | Cast needed by caller | Yes (pre-covariant) / fragile | No | | Works with final fields | Often conflicts | Yes | | Relies on Cloneable contract | Yes (fragile marker) | No | | Can convert representation | No (preserves runtime class) | Yes (interface-typed arg) | That last row is powerful: a **conversion constructor** takes an interface type, so you can copy *and* change implementation: `List<T> copy = new ArrayList<>(anyCollection);` or `Set<T> sorted = new TreeSet<>(anySet);`. `clone()` can never do this because it always reproduces the original's exact runtime class. ## Shallow vs deep — the one thing to get right The single mistake to avoid is copying a *reference* to a mutable field instead of a fresh instance. If `Period`'s copy constructor did `this.start = other.start;`, the two `Period`s would share the same `Date`, and mutating it through one would corrupt the other. Always deep-copy mutable members (and, where appropriate, deep-copy on the way *out* of getters too). For **immutable** fields (`String`, `int`, other immutable types) sharing the reference is fine and even preferred — there's nothing to mutate. ## Bottom line Prefer a copy constructor or copy factory. They're clearer, type-safe, exception-free, compatible with `final` and immutability, and uniquely able to convert between representations — which is exactly why the JDK collections all ship copy constructors and *Effective Java* steers you away from `clone()`.

  • When is it safe to share a field reference in a copy constructor instead of deep-copying?
    When the field is immutable (String, Integer, an immutable value type, or any object guaranteed never to be mutated). There's nothing to defend against, so sharing the reference is correct and avoids needless allocation. Deep-copy only mutable members.
  • Give a concrete example of a conversion constructor from the JDK.
    new ArrayList<>(someCollection) or new TreeSet<>(someCollection): each takes a Collection (an interface) and produces a copy in a specific implementation, copying the elements while switching representation — something clone() cannot do because it preserves the source's exact runtime type.

saying these in an interview costs you the question

  • Copying a mutable field by reference instead of deep-copying it
  • Claiming copy constructors skip validation like clone() does (they don't)
  • Deep-copying immutable fields like String unnecessarily
  • Saying clone() can change the collection's implementation type (it can't)

context