Share vs Copy on Assign
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.
1. Reference semantics. Car a = new Car(); Car b = a; How many Car objects exist?
Check
One. b = a copies the reference (the address of the object), not the object itself. Both a and b point to the same Car instance.
2. Mutation through alias. After Car a = new Car(); Car b = a; b.setColor("red");, what color is a?
Check
"red". a and b reference the same object. Mutating through b is the same as mutating through a.
3. Immutability. String is immutable. Can a caller change the content of a String object that is stored as a field?
Check
No. String has no methods that change its character sequence. Sharing a String reference is safe because the caller cannot mutate the string through their reference.
Try This First
List<Integer> external = new ArrayList<>(List.of(1, 2, 3));
Container c = new Container(external); // constructor: this.data = external;
external.add(4);
System.out.println(c.size()); // assume c.size() returns this.data.size()
Predict the output.
Check
4. this.data = external stores the same reference. external.add(4) mutates the shared list. c.size() reads the same (now size-4) list.
What You Need To Walk In With
The Insight: this.x = x; in Java copies the reference (the address), not the object. After this assignment, this.x and the caller’s x point to the same object in memory. Any mutation through either reference is visible through the other. To get copy semantics (independent objects), you must explicitly construct a new object or call a copy method. The same aliasing rule explains a tool you already use every day: in Git is a linked structure, a branch is one shared reference to a commit, not a copy, which is why branching costs nothing.
Coming in, you should be able to look at any assignment and say whether two variables name the same object or different ones, explain why mutating through one reference affects the other when they share, and recognize which types in the Java standard library (String, enum constants, primitives) are safe to share because they cannot be mutated at all. You can: predict aliasing outcomes before running code, write a copy-on-assign constructor for a mutable type, and explain why S20.Lab2.Person shares while S20.Lab3.Book copies.
How It Works
Share semantics: this.x = x;
public class Container {
private List<Integer> data;
public Container(final List<Integer> data) {
this.data = data; // SHARE: stores the same reference
}
}
After the constructor returns:
this.datainsideContainerand the caller’sdatavariable both point to the sameArrayListobject.- Any call to
data.add(...)from the caller mutates the same list thatContainer.datareads. - The container has no isolation from the caller.
Copy semantics: this.x = new X(x);
public class Container {
private List<Integer> data;
public Container(final List<Integer> data) {
this.data = new ArrayList<>(data); // COPY: new object, same content
}
}
After the constructor returns:
this.datainsideContainerand the caller’sdatavariable point to differentArrayListobjects.- Calling
data.add(...)from the caller does not affectthis.data. - The container is isolated.
The design decision: which to use?
The rule: copy for mutable types; share is safe for immutable types.
| Type | Mutable? | Share safe? | Action |
|---|---|---|---|
String |
No | Yes | Share (this.name = name) |
int, boolean (primitive) |
No (primitives) | Yes | Share |
| enum constant | No | Yes | Share |
ArrayList, Date, custom class |
Usually yes | Only if intended | Copy (defensive) |
S20.Lab2.Person stores String fn, String ln, Color color (an enum). All three are immutable. Sharing is safe. S20.Lab3.Book stores Publisher pub and Author[] authors, which are mutable. The constructor uses new Publisher(...) and new Author(...) to make independent copies.
Quick check
Check your understanding
A constructor does this.data = new ArrayList<>(data); instead of this.data = data;. What is the immediate effect?
new ArrayList<>(data) constructs a fresh object with the same contents. After this line, this.data and the caller’s data reference two different ArrayList instances on the heap, so mutations through one do not affect the other.Worked Example: Predict Then Check
// Scenario A: share
Publisher orig = new Publisher("O'Reilly", "Sebastopol");
class BookA {
Publisher pub;
BookA(Publisher p) { this.pub = p; } // share
}
BookA a = new BookA(orig);
orig.setPubName("Different");
System.out.println(a.pub.getPubName());
// Scenario B: copy
class BookB {
Publisher pub;
BookB(Publisher p) { this.pub = new Publisher(p.getPubName(), p.getPubCity()); } // copy
}
BookB b = new BookB(orig);
orig.setPubName("Another");
System.out.println(b.pub.getPubName());
Predict both outputs after both mutations.
Reasoning
Scenario A: a.pub and orig are the same object. After setPubName("Different"), a.pub.getPubName() returns "Different".
Scenario B: b.pub is a fresh Publisher created from orig’s data before setPubName("Another"). At the time b was constructed, orig was named "Different". The copy captured that name. After setPubName("Another"), b.pub is unchanged.
Show answer
Scenario A: "Different" (shared reference, mutation visible). Scenario B: "Different" (the copy captured the name that existed when b was constructed; subsequent mutations to orig do not affect b.pub).
Common Misconceptions
Misconception 1: = copies the object
Wrong mental model: “
this.customer = customer;gives me my own copy of the customer.”
Why it breaks: Java’s = for reference types copies the bit pattern of the reference (the memory address), not the object. Both the field and the original variable now point to the same heap object. Mutations through either path affect the same object.
How to correct: To get a copy, write it explicitly: this.customer = new Customer(customer.getName(), ...). There is no automatic copying in Java for reference types.
Source: JLS §15.26.
Quick check
Check your understanding
After this.publisher = publisher; in a constructor, the caller changes the publisher’s name via publisher.setName("New Name"). What does this.publisher.getName() return?
this.publisher = publisher; copies the reference, not the object. Both the field and the caller’s variable point to the same Publisher on the heap. When the caller calls setName, that mutation is immediately visible through this.publisher because they are the same object.Misconception 2: defensively copying immutable types
Wrong mental model: “I should copy every reference parameter to be safe.”
Why it breaks: Immutable types (String, Integer, enum constants, BigDecimal) cannot be mutated by the caller after the assignment. Copying adds an allocation with no benefit. new String(name) is noise; this.name = name is correct and idiomatic.
How to correct: Ask “can the caller change this object’s content after handing it to me?” If the type is immutable, the answer is no. Share directly. Reserve defensive copies for mutable types.
Source: Bloch, Effective Java, Item 17.
Formal Definition and Interface Contract
From JLS §15.26 (Assignment Operators):
A simple assignment expression has a value that is the value of the right-hand side of the assignment after any implicit type conversion. [...] For a reference type, the value assigned is a reference to an object.
Bloch, Effective Java, 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. [...] If a class contains mutable objects, you may need to make defensive copies of those objects.
Mental Model
Think of a reference as a card with the object’s address written on it. this.x = x makes a copy of the card (same address, not the same object), and puts that copy in the field slot. Both cards lead to the same house. Writing a new X(x) builds a new house with the same furniture as the original, then gives you the address of the new house. Now you have two separate houses; modifying one does not change the other.
Connections
Within CSCD 210/211: S20.Lab2.Person uses share semantics (all three fields are immutable types). S20.Lab3.Book uses copy semantics for Publisher and Author. This lesson explains why the two labs make different choices.
Looking back: Declaring and Initializing a Class-Type Field established HOW to set a field. This lesson establishes WHAT that assignment does at the reference level.
Looking ahead: The defensive copy concept covers copying in detail. Null Handling on Composed Fields covers the complementary question: what to do when the reference parameter is null.
Practice
Level 1
Predict whether c.size() prints 3 or 4:
// Version A (share)
class Container { List<Integer> data; Container(List<Integer> d) { this.data = d; } int size() { return data.size(); } }
List<Integer> list = new ArrayList<>(List.of(1,2,3));
Container c = new Container(list);
list.add(4);
System.out.println(c.size()); // A: ?
// Version B (copy)
class Container { List<Integer> data; Container(List<Integer> d) { this.data = new ArrayList<>(d); } int size() { return data.size(); } }
list = new ArrayList<>(List.of(1,2,3));
c = new Container(list);
list.add(4);
System.out.println(c.size()); // B: ?
Show answer
A: 4 (shared reference, mutation visible). B: 3 (independent copy, mutation not visible).
Level 2
For each field in S20.Lab2.Person (String fn, String ln, Color color), state whether sharing is safe or copying is needed. Justify each decision.
Show answer
String fn: sharing is safe.Stringis immutable; the caller cannot change the character sequence through their reference.String ln: same reasoning. Sharing safe.Color color: sharing is safe.Coloris an enum; enum constants are singletons and immutable.
None of the three types is mutable, so no defensive copy is needed. this.fn = fn; this.ln = ln; this.color = color; is correct and sufficient.
Source: S20.Lab2.Person.java; Bloch, Effective Java, Item 17.
Level 3
S20.Lab3.Book has a field Publisher pub. The constructor receives a Publisher pubs parameter and does this.pub = new Publisher(pubs.getPubName(), pubs.getPubCity()) instead of this.pub = pubs. Explain why the copy form is required here but the share form is sufficient in S20.Lab2.Person.
Show answer
Publisher is a mutable class: it has setters that can change its name and city after construction. If Book stored the caller’s pubs reference directly, the caller could call pubs.setPubName("Fake Publisher") and silently change the Book’s publisher without the Book’s knowledge, violating the Book’s invariant. The defensive copy creates a fresh Publisher from the current data; subsequent mutations to pubs cannot reach the Book’s internal Publisher.
String and enum constants in Person are immutable: no method exists that can change a String’s character sequence or an enum constant’s value. Sharing is safe because the caller has no mechanism to mutate the shared object.
Source: S20.Lab3.Book.java; Bloch, Effective Java, Item 50.
Go Deeper (optional)
None of this depth is needed to write correct code. If curiosity pulls you further, here is where this concept connects to larger ideas.
The share-vs-copy decision is the origin of most data-aliasing bugs in professional Java code: two modules unknowingly hold references to the same mutable object, and one module’s changes silently corrupt the other’s view of the data. Making the decision explicit in the constructor (share or copy) is how experienced engineers prevent a whole class of maintenance nightmares. In code reviews at most companies, a constructor that accepts a mutable collection and assigns it directly without comment is a red flag that gets questioned. If you run the two Container scenarios in jshell (type jshell at the terminal and paste each variant), you will see the concrete numeric difference produced by changing exactly one line of constructor code, which makes the abstract rule immediate.
Going further: the mutable-vs-immutable split connects to ideas you will encounter by name in later courses. Passing a callable object that decides how two things compare is the Strategy pattern (Gang of Four, 1994); a Comparator is exactly that, a binary relation that, if it satisfies reflexivity, antisymmetry, and transitivity, imposes a strict total order. Those three axioms are the formal contract Comparator.compare must obey for Collections.sort to produce correct output, connecting this corner of Java to order theory. Closer to home, has-a composition (a Book HAS-A Publisher) corresponds in type theory to a product type: a Book value is a tuple of its field values, so the set of possible Book values is the Cartesian product of the possible Publisher values and the possible Author values. This is why composition is called “product type” in languages such as Haskell or Rust. The idea that each class should own exactly one well-defined responsibility traces to David Parnas (1972), who argued that modules should be designed around information hiding rather than convenience groupings. The single-responsibility principle you encounter in CSCD 211 is a direct descendant of that paper.
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
In Java, what does this.data = data; copy when data is a reference type such as ArrayList?
this.data = data;, both the field and the caller’s variable point to the same ArrayList on the heap. JLS 15.26 states that the value assigned for a reference type is a reference to an object.A Container constructor does this.data = data; (share). After construction, the caller calls data.add(99). What does c.size() return if the original list had three elements?
this.data and the caller’s data reference the same ArrayList. Calling data.add(99) mutates that single object. When c.size() reads this.data.size(), it sees all four elements.For which of the following field types is sharing a reference always safe, without any defensive copy?
Trace this code and predict both printed lines:
``java
Publisher orig = new Publisher("O'Reilly", "Sebastopol");
class BookA { Publisher pub; BookA(Publisher p) { this.pub = p; } }
class BookB { Publisher pub; BookB(Publisher p) { this.pub = new Publisher(p.getPubName(), p.getPubCity()); } }
BookA a = new BookA(orig);
BookB b = new BookB(orig);
orig.setPubName("Different");
System.out.println(a.pub.getPubName());
System.out.println(b.pub.getPubName());
``
a.pub and orig point to the same Publisher. After setPubName("Different"), a.pub.getPubName() returns “Different”. BookB copies: b.pub is a fresh Publisher created when orig was still “O’Reilly”. Subsequent mutations to orig do not reach b.pub, so it still returns “O’Reilly”.In S20.Lab2.Person, the fields are String fn, String ln, and Color color (an enum). The constructor does this.fn = fn; this.ln = ln; this.color = color; with no defensive copies. Which statement best justifies this choice?