Defensive Copy in the Constructor
Before You Start
Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.
Not sure? Take the 60-second self-check.
- After
Address a = new Address("1 Main", "Cheney"); Address b = a;, how manyAddressobjects exist in memory?
Check
One. Both a and b refer to the same object. Assigning a reference copies the reference, not the object.
- A class
Publisherhas fieldspubNameandpubCitywith matching gettersgetPubName()andgetPubCity(), and a two-argument constructorPublisher(String name, String city). Write the single expression that produces a newPublishercarrying the same data as an existingPublisher p.
Check
``java new Publisher(p.getPubName(), p.getPubCity()) ``
This reads the two values out of p and feeds them into a fresh object. p is not touched afterward.
- If a caller holds a reference to an
Addressobject and your class stores that same reference as a private field, can the caller change your field’s data without calling any method on your class?
Check
Yes. The caller can call setCity("Spokane") on its own reference, and your private field reflects the change immediately, because both variables point at the same object. Private visibility does not protect against this.
What You Need To Walk In With
You need to know that assigning a reference copies the pointer, not the object it points to, and that private visibility protects the field name from outside code but does not prevent outside code from mutating the object the field refers to. You also need to know how to call an accessor and pass its return value to a constructor. Once those two ideas are in place, the defensive copy pattern is a single line of consequence that follows directly.
You can: identify which fields in a class need defensive copying (mutable reference types) versus which do not (primitives and immutable types like String), state the two requirements a contained type must meet for the new-from-data pattern to work, write a correct defensive copy in a constructor given a class with mutable composed fields and their accessors, and write a short test that reveals the defect when the copy is omitted.
Try this first
You are given the following skeleton. Predict what gets printed before revealing the answer.
class Address {
private String city;
Address(String city) { this.city = city; }
void setCity(String c) { this.city = c; }
String getCity() { return city; }
}
class Customer {
private Address address;
Customer(Address a) {
this.address = a; // direct assignment, no copy
}
String getCity() { return address.getCity(); }
}
// In a main method:
Address home = new Address("Cheney");
Customer c = new Customer(home);
home.setCity("Spokane");
System.out.println(c.getCity());
Write down your prediction, then reveal the answer.
What prints?
Spokane.
Even though address is a private field of Customer, the constructor stored the caller’s reference. When the caller mutates home, Customer.address reflects it immediately. The word “private” protects the field name from outside code; it does not protect the object the field points to.
How it works
The problem in one diagram
After this.address = a;
home ──────────────────┐
▼
[ Address: "Cheney" ]
▲
c.address ─────────────┘
Calling home.setCity("Spokane") changes the shared object.
Both home and c.address now see "Spokane".
After this.address = new Address(a.getCity());
home ──────► [ Address: "Cheney" ] (caller's copy)
c.address ─► [ Address: "Cheney" ] (Customer's private copy)
Calling home.setCity("Spokane") changes only the first object.
c.address still sees "Cheney".
The canonical pattern (S20.Lab3.Book)
The Lab 3 Book class holds a Publisher as a composed field. Its primary EVC does this:
this.pub = new Publisher(pubs.getPubName(), pubs.getPubCity());
pubs is the constructor parameter. The class reads pubName and pubCity from pubs through its public accessors, then passes those values to Publisher’s own constructor to build a new object. After this line runs, Book.pub points to an object that no caller holds a reference to.
Two requirements
For the new-from-data pattern to work, the contained type must satisfy both conditions:
| Requirement | Example |
|---|---|
| Accessors exist for every value needed to reconstruct it | getPubName(), getPubCity() |
| A constructor (or static factory) accepts those values | Publisher(String name, String city) |
If the contained type offers neither accessors nor a suitable constructor, you may be tempted to call clone(). That approach has well-documented problems (Bloch, Effective Java, Item 13): clone returns Object, requiring an unchecked cast, and a broken or shallow clone in a subclass silently breaks your copy. Adding accessors and a copy constructor to the contained type is the preferred fix.
Copy constructors
A copy constructor takes an instance of its own type and copies all needed state:
// Copy constructor for Publisher
public Publisher(Publisher other) {
this(other.getPubName(), other.getPubCity());
}
Callers can then write:
this.pub = new Publisher(pubs);
This is the form Bloch recommends (Effective Java, Item 13) as a cleaner alternative to clone.
Quick check
Check your understanding
A constructor receives a mutable Publisher parameter pubs. Which single line correctly stores a defensive copy in the field this.pub?
Worked example
Predict, then check
A class Event has fields title (String, immutable) and location (an Address, mutable). The goal is to write a defensive constructor for Event.
Step 1. Identify which fields are mutable reference types. String is immutable, so title needs no copy. Address is mutable, so location does.
Step 2. Confirm Address satisfies both requirements. It has getStreet() and getCity() and a constructor Address(String street, String city). Both requirements are met.
Step 3. Write the constructor:
class Event {
private String title;
private Address location;
public Event(String title, Address location) {
this.title = title; // String: immutable, direct assign OK
this.location = new Address(location.getStreet(), // Address: mutable, copy required
location.getCity());
}
}
Step 4. Verify the fix. After the constructor returns, Event.location points to an object no caller holds. Calling setCity(...) on the original Address does not affect the Event.
Predict: After event.location = new Address(loc.getStreet(), loc.getCity()); runs, what is the reference count on the caller’s Address object?
Check
Still 1: the caller’s own variable. The constructor created a separate object and stored that. The caller’s object is unaffected regardless of what the Event does internally.
Common misconceptions
assign first, then overwrite
Wrong mental model: “This works the same way.”
this.pub = pubs; // assign caller ref
this.pub = new Publisher(this.pub.getPubName(), this.pub.getPubCity()); // then overwrite
Why it breaks on a small example: if the constructor body throws an exception between those two lines (for example, getPubName() returns null and a later check throws), the partially-constructed object temporarily holds the caller’s reference. Code that inspects the partially-constructed object during stack unwinding sees the unprotected alias. More critically, this ordering signals to readers that aliasing was intended, then reversed, which is confusing and error-prone.
How to correct: combine both into a single assignment: this.pub = new Publisher(pubs.getPubName(), pubs.getPubCity()); No intermediate state, one clear intent.
Source: practitioner observation; Bloch, Effective Java 3rd ed., Item 50.
Quick check
Check your understanding
A developer writes two lines in a constructor: first this.pub = pubs; then this.pub = new Publisher(this.pub.getPubName(), this.pub.getPubCity());. What is wrong with this approach compared to doing it in a single assignment?
clone() is a safe defensive copy
Wrong mental model: “this.pub = (Publisher) pubs.clone(); copies the object, so it is safe.”
Why it breaks on a small example: clone() returns Object, requiring an unchecked cast. If Publisher is subclassed and the subclass overrides clone() incorrectly, or if clone() performs a shallow copy, the “copy” is still vulnerable to mutations of shared nested objects. Bloch documents the fragility of clone in detail (Item 13).
How to correct: add accessors and a copy constructor to the contained type; use the new-from-data form. If refactoring the contained type is not possible, document the clone workaround explicitly and treat it as technical debt.
Source: Bloch, Effective Java 3rd ed., Item 13; Item 50.
shallow copy is always sufficient
Wrong mental model: “new ArrayList<>(list) defensively copies a list.”
Why it breaks on a small example: new ArrayList<>(list) creates a new list object, but each element in the new list is the same reference as the corresponding element in the old list. If the elements are mutable, the caller can mutate them through the old list’s elements, and your copy reflects those changes.
How to correct: decide on the depth you need. For a list of immutable elements (like String), new ArrayList<>(list) is correct. For a list of mutable elements, copy each element individually:
List<Address> copy = new ArrayList<>();
for (Address a : addresses) {
copy.add(new Address(a.getStreet(), a.getCity()));
}
this.locations = copy;
Source: Bloch, Effective Java 3rd ed., Item 13; Item 50.
Formal definition and interface contract
The pattern is grounded in Bloch, Effective Java 3rd edition, Item 50 (“Make defensive copies when needed”):
“You must program defensively, with the assumption that clients of your class will do their best to destroy its invariants.”
Item 50 specifically addresses the constructor case:
“To protect the internals of a Period instance from this sort of attack, it is essential to make a defensive copy of each mutable parameter to the constructor.”
Item 13 (“Override clone judiciously”) explains why copy constructors and static factories are preferred over clone:
“A fine approach to object copying is to provide a copy constructor or copy factory.”
The Java 25 API specification does not mandate defensive copying, but the java.util.Date documentation warns that Date is mutable and callers should be aware of aliasing. The general principle applies to any mutable type a class holds by composition.
Mental model
Think of a field as a name tag, not a container. When you write this.pub = pubs;, you are pinning your name tag onto the caller’s object. Both you and the caller can reach that object. When you write this.pub = new Publisher(pubs.getPubName(), pubs.getPubCity());, you are building your own separate object and pinning your name tag to that. The caller still has their object; you have yours. Outside code cannot reach yours because nobody else holds a reference to it.
Connections
Within CSCD 211: Element-by-Element Array Copy extends this pattern to the return direction. Immutable class design shows how to eliminate the need for defensive copying by making the contained type incapable of mutation.
Looking back: Immutable Fields Need No Defensive Copy established why two references to the same object can cause unexpected behavior. Constructor copy is the fix applied at construction time.
Looking ahead: After learning constructor copy and getter copy together, you have the tools to reason about encapsulation in terms of object graphs, not just field visibility. That reasoning recurs in every topic that involves shared mutable state: collections, generics, and concurrent programming.
Go Deeper (optional)
None of what follows is needed to write correct defensive copies. If the code above is solid, you are done. This section is for when you want to know where the idea fits in the larger map.
Why clone is broken by design. Bloch devotes Item 13 of Effective Java to this question, and the answer spans several layers. Object.clone returns Object, so every call requires an unchecked cast. The Cloneable interface does not declare clone, so there is nothing to put in a type signature. And because clone is inherited, a subclass can override it shallowly or incorrectly, and your code silently receives a broken copy with no compiler warning. Reading Item 13 in full is one of the most instructive 20 minutes in Java because it shows what happens when a feature is designed without a consistent contract.
The function-object idea behind copy constructors. A copy constructor that takes Publisher(Publisher other) and reads other.getPubName() is an example of treating the type as data: you extract what you need and build a new instance. This same idea is the Strategy pattern (passing behavior as an object) and the simplest form of a first-class function. Recognizing it here makes the Strategy pattern easier to understand when it appears by name later.
Aliasing in other languages. The aliasing problem is not Java-specific; it appears in any language with mutable reference semantics. Python, Go, and C++ with raw pointers all have the same hazard. Rust’s borrow checker enforces the absence of aliasing mutations at the type-system level, making the problem structurally impossible in safe Rust. If you ever look at a Rust ownership diagram, it is the same two-arrow picture from the diagram above, made into a compile-time rule.
Where this appears in professional code. In enterprise Java, defensive copying shows up constantly in value objects: an Order copying a ShippingAddress, an Invoice copying LineItem data, a configuration object copying a mutable Properties map. Knowing the pattern by name (“defensive copy,” “copy constructor”) and being able to cite Bloch Item 50 is a concrete talking point in technical interviews, because it shows you think about invariant preservation, not just syntax.
Practice
Level 1
You are given:
class Point {
private int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
public void translate(int dx, int dy) { x += dx; y += dy; }
}
class Circle {
private Point center;
private int radius;
// TODO: write this constructor
public Circle(Point center, int radius) { ... }
}
Write the body of Circle’s constructor so that mutations to the caller’s Point after construction do not affect the Circle’s center.
Thought process
Point is mutable (it has translate). Storing the caller’s reference allows the caller to move the center after the Circle is built. The fix: use Point’s accessors (getX(), getY()) to build a fresh Point.
int radius is a primitive: no copy is needed.
Show answer
public Circle(Point center, int radius) {
this.center = new Point(center.getX(), center.getY());
this.radius = radius;
}
After this constructor returns, Circle.center refers to an object no caller holds a reference to. Calling center.translate(1, 0) on the caller’s Point does not affect the Circle.
Level 2
The stem below has a constructor that defensively copies a Publisher. Explain what happens at runtime if the developer instead wrote this.pub = pubs;, then trace through a small test to show the problem.
// Current (correct) version in S20 Lab 3:
public Book(String title, Publisher pubs, int year) {
this.title = title;
this.pub = new Publisher(pubs.getPubName(), pubs.getPubCity());
this.year = year;
}
Write the broken version and then the test that reveals the defect.
Thought process
Replace the defensive copy with a direct assignment. Then write a test that constructs a Book, mutates the original Publisher, and checks whether the Book’s data changed. The test should fail (or produce wrong output) with the broken version and pass (or produce correct output) with the fixed version.
Show answer
Broken constructor:
public Book(String title, Publisher pubs, int year) {
this.title = title;
this.pub = pubs; // alias -- not a copy
this.year = year;
}
Revealing test:
Publisher p = new Publisher("EWU Press", "Cheney");
Book b = new Book("Data Structures", p, 2024);
p.setPubCity("Spokane"); // mutate after construction
System.out.println(b.getPub().getPubCity()); // prints "Spokane" -- wrong
With the correct constructor, the final print would produce "Cheney", because b.pub points to a separate object.
Level 3
A class Schedule holds a List<Event> as a composed field. Each Event is mutable. Write a constructor for Schedule that defensively copies the list so that:
- Adding or removing events from the caller’s list after construction does not affect the
Schedule. - Mutating an individual
Eventobject from the caller’s list does not affect theSchedule’s copy.
Assume Event has accessors getTitle() and getDate() and a constructor Event(String title, LocalDate date). LocalDate is immutable.
Thought process
Two levels of mutation to defend against:
- Structural (add/remove on the list): a new list object blocks this.
- Element-level (mutating an
Eventin the list): a new list does not block this, because the elements in the new list are still the same objects. EachEventmust also be copied.
LocalDate is immutable, so copying it by reference in the Event copy is fine.
Show answer
class Schedule {
private List<Event> events;
public Schedule(List<Event> events) {
List<Event> copy = new ArrayList<>();
for (Event e : events) {
copy.add(new Event(e.getTitle(), e.getDate())); // deep copy: new Event per element
}
this.events = copy;
}
}
After this constructor returns:
this.eventsis a differentListobject than the caller’s list. Adding/removing from the caller’s list has no effect.- Each
Eventinthis.eventsis a new object. Mutating anEventin the caller’s list has no effect on theSchedule’s copies.
If Event elements were immutable, new ArrayList<>(events) would suffice (shallow structural copy). Because they are mutable, element-level copying is required. This matches the guidance in Bloch, Effective Java 3rd ed., Item 50.
Check Yourself
Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
A class stores a mutable Address field. Its constructor contains this.address = a; where a is the constructor parameter. After construction, the caller calls a.setCity(“Spokane”). What does this.address.getCity() now return?
Which two conditions must a contained type satisfy before you can use the new-from-data defensive copy pattern?
Why does Bloch (Effective Java, Item 13) recommend copy constructors over clone() for defensive copying?
Trace this code and write down what prints: class Point { private int x; Point(int x) { this.x = x; } void setX(int v) { this.x = v; } int getX() { return x; } } class Box { private Point corner; Box(Point p) { this.corner = new Point(p.getX()); // defensive copy } int getX() { return corner.getX(); } } // in main: Point origin = new Point(0); Box b = new Box(origin); origin.setX(99); System.out.println(b.getX());
A Schedule holds a List<Event> where Event is mutable. The constructor does this.events = new ArrayList<>(incoming); A caller then mutates an Event object they still hold a reference to. What happens to the Schedule’s copy of that Event?