← Skill tree CS Skill Tree 0 CSCD211

Share vs Copy on Assign

Textbook: BJP (Reges and Stepp)

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:

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:

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?

Tier 2 · BJP (Reges and Stepp), Ch 8


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?

Tier 2 · BJP (Reges and Stepp), Ch 8


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. String is immutable; the caller cannot change the character sequence through their reference.
  • String ln: same reasoning. Sharing safe.
  • Color color: sharing is safe. Color is 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?

Tier 1 · BJP (Reges and Stepp), Ch 8

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?

Tier 1 · BJP (Reges and Stepp), Ch 8

For which of the following field types is sharing a reference always safe, without any defensive copy?

Tier 2 · BJP (Reges and Stepp), Ch 8

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()); ``

Tier 2 · BJP (Reges and Stepp), Ch 8

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?

Tier 3 · BJP (Reges and Stepp), Ch 8