Copy Constructors and Deep Copies

OOP · lesson 35 of 43 · 3 min read

Why clone() is a trap, how a copy constructor reads, and where a shallow copy quietly shares state.

Open this lesson in the learning hub

Key points

  • Assigning a variable copies the reference only: two names, one object, and every change shows up through both.
  • Object.clone() is protected, needs Cloneable, and copies one level deep. Most teams avoid it.
  • A copy constructor is ordinary code: new Team(other). Visible, testable, and fine with final fields.
  • A shallow copy shares every mutable field. Copy the collections and mutable members too, or the copy is not independent.
  • A static factory named copyOf reads even better, and it may return the same instance when the value is immutable.
  • If the object is immutable there is nothing to copy. That is the cheapest way to end this argument for good.

Example

import java.util.ArrayList;
import java.util.List;

public class Main {

    static final class Team {
        private final String name;
        private final List<String> members;          // a mutable member

        Team(String name, List<String> members) {
            this.name = name;
            this.members = members;                  // no copy: shallow on the way in
        }

        Team(Team other) {                           // COPY CONSTRUCTOR, done properly
            this.name = other.name;
            this.members = new ArrayList<>(other.members);
        }

        void add(String person) { members.add(person); }

        @Override public String toString() { return name + members; }
    }

    public static void main(String[] args) {
        List<String> people = new ArrayList<>(List.of("Ada"));

        Team original = new Team("Core", people);
        Team shared = new Team("Core", people);      // shallow: the very same list
        Team deep = new Team(original);              // copy constructor: its own list

        original.add("Grace");

        System.out.println("original       " + original);
        System.out.println("shallow sharer " + shared);   // followed the change
        System.out.println("deep copy      " + deep);     // did not

        System.out.println("shares list?   " + (original.members == shared.members));
        System.out.println("shares list?   " + (original.members == deep.members));
    }
}

Write a copy constructor, copy the mutable parts inside it, and leave clone alone.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.