← Skill tree CS Skill Tree 0 CSCD211

Immutable Fields Need No Defensive Copy

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. Sharing risk. When is sharing a reference field with the caller dangerous?

Check

When the caller can mutate the object through their reference, causing the class’s invariants to be violated without the class being notified.

2. Immutable type. Can the caller change the contents of a String object after passing it to a constructor?

Check

No. String has no mutation methods. The caller cannot change the character sequence of an existing String object.

3. final on a field. private final List<String> items; in a class. Can items.add("x") succeed?

Check

Yes. final prevents the reference from being reassigned (items = someOtherList fails), but the object pointed to by items is still mutable. items.add("x") mutates the list’s contents.


Try This First

S20.Lab2.Person has three reference fields: String fn, String ln, Color color (an enum). The constructor assigns all three directly:

this.fn = fn; this.ln = ln; this.color = color;

Before reading: does this constructor need defensive copies for any of the three fields?

Check

No. All three types are immutable. String has no mutation methods. Enum constants are singletons with no mutation methods. The caller cannot change what fn, ln, or color refer to through their parameter references.


What You Need To Walk In With

The Insight: Defensive copying protects against caller mutation. If the type cannot be mutated, there is nothing to protect against. The decision rule is: “Can the caller mutate this object’s state through their reference?” If no (immutable type), share directly. If yes (mutable type), copy. Applying defensive copy to immutable types adds allocation overhead with zero benefit.

If memorizing a list of immutable types feels fragile, apply the question directly: “After the caller hands this object to me, can the caller call any method that changes its internal state?” If no such methods exist, the type is effectively immutable for this purpose. That question works even for types you have never seen before.

You can: classify a given type as immutable or mutable and justify the classification; apply the share-vs-copy decision rule to each field in a multi-field class; explain why new String(name) is unnecessary for a String field; and explain why final on a reference field does not make the referenced object immutable.


How It Works

Immutable types in CSCD 211 labs

Type Immutable? Why
int, double, boolean (primitives) Yes (no reference) Pass-by-value; no shared object
String Yes All mutation-like methods return new Strings
Integer, Double, Boolean (wrappers) Yes No mutation methods
Enum constants Yes Each constant is a singleton with no setters
BigDecimal, BigInteger Yes All arithmetic returns new instances
LocalDate, Instant (Java 8+ date/time) Yes Immutable by design
Date (legacy) No Has setTime(long)
int[], String[] (arrays) No Elements can be reassigned
ArrayList<T>, LinkedList<T> No add, set, remove mutate in place

S20 Lab 2 Person: all-immutable fields

public Person(final String fn, final String ln, final Color color)
{
    if (fn == null || ln == null || color == null)
        throw new IllegalArgumentException("Bad parameter(s) in Person EVC");
    this.fn    = fn;      // String is immutable: share is safe
    this.ln    = ln;      // String is immutable: share is safe
    this.color = color;   // enum constant: share is safe
}

Direct assignment for all three. No new String(fn), no clone, no copy constructor. The caller cannot change the content of fn, ln, or color after construction.

final vs. immutability: a critical distinction

private final List<String> items;

public MyClass(final List<String> items) {
    this.items = items;  // final reference; mutable object
}

After construction, this.items = someOtherList fails: final prevents reference reassignment. But this.items.add("new entry") succeeds: the object the reference points to is still mutable. final protects the field slot, not the object.

For genuine immutability, use an immutable type or a copy:

this.items = List.copyOf(items);   // immutable view (Java 10+)

Quick check

Check your understanding

A constructor receives a parameter of type ‘LocalDate orderDate’. According to the share-vs-copy decision rule, what should the constructor do?

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


Worked Example: Predict Then Check

For each field declaration and constructor assignment, state whether defensive copy is needed:

// (a)
private String name;
public Foo(String name) { this.name = name; }

// (b)
private int count;
public Foo(int count) { this.count = count; }

// (c)
private Genre genre;     // Genre is an enum
public Foo(Genre genre) { this.genre = genre; }

// (d)
private Date createdAt;  // legacy java.util.Date
public Foo(Date createdAt) { this.createdAt = createdAt; }

// (e)
private List<String> tags;
public Foo(List<String> tags) { this.tags = tags; }
Step-by-step reasoning

(a) String is immutable. No copy needed. (b) int is a primitive. No reference; no object to protect. (c) Genre is an enum constant. No mutation methods. No copy needed. (d) Date has setTime(long); the caller can mutate it. Copy required. (e) List<String> has add, set, remove. The caller can mutate it. Copy required.

Show answers
Field Copy needed?
(a) String name No
(b) int count No
(c) Genre genre No
(d) Date createdAt Yes
(e) List<String> tags Yes

Common Misconceptions

Misconception 1: defensively copying String with new String(...)

Wrong mental model: “The rule says to defensive-copy constructor parameters. this.title = new String(title) is defensive copy for a String field.”

Why it breaks: String is immutable. new String(title) allocates a new String object that contains the same characters as title. The caller cannot mutate title’s character sequence, so there is nothing to protect against. The allocation is wasted. Modern lints (SpotBugs rule DM_STRING_CTOR) flag this pattern.

How to correct: this.title = title; is the correct and idiomatic form. Defensive copy applies only to mutable types.

Source: Bloch, Effective Java, Item 17.

Quick check

Check your understanding

A student writes ‘this.isbn = new String(isbn);’ in a constructor where isbn is declared as a String parameter. Which statement best describes this code?

Tier 1 · Bloch, Effective Java, Item 17


Misconception 2: final makes the referenced object immutable

Wrong mental model:private final List<String> items means items is immutable; I do not need to copy.”

Why it breaks: final on a reference field means the field cannot be reassigned (this.items = anotherList is a compile error). It says nothing about the content of the list. this.items.add("x") and this.items.clear() both compile and run successfully. The referenced object remains mutable.

How to correct: For genuine immutability of a list field, store an immutable view:

this.items = List.copyOf(items);   // unmodifiable in Java 10+

Or: this.items = Collections.unmodifiableList(new ArrayList<>(items)); in Java 8/9.

Source: Bloch, Effective Java, Item 17.


Formal Definition

From Java 25 String API:

String objects are constant; their values cannot be changed after they are created.

From Bloch, Effective Java, Item 17 (Minimize mutability):

Classes should be immutable unless there’s a very good reason to make them mutable. [...] Never provide methods that modify the object’s state. [...] Make all fields final. [...] Make all fields private. [...] Ensure exclusive access to any mutable components.

An immutable class makes all five requirements true simultaneously. For types that satisfy all five, defensive copying is unnecessary.


Mental Model

Think of immutable objects as sealed packages. Once sealed, no one can change the contents from outside. Handing a sealed package to a constructor and storing the reference is safe: the caller cannot open the package and change what’s inside. For mutable objects, the package is open: the caller can reach in and change the contents after handing it over. That is when a defensive copy is needed.


Connections

Within CSCD 210/211: S20.Lab2.Person is the all-immutable-field example. S20.Lab3.Book is the mixed example: String title and String isbn are immutable (no copy); Publisher pub and Author[] authors are mutable (copy needed).

Looking back: Share vs Copy on Assign established the general rule. This lesson applies the rule to the immutable-type case.

Looking ahead: Element-by-Element Array Copy covers the hardest defensive-copy case: arrays of mutable elements, where copying the outer array is insufficient.


Go Deeper (optional)

None of the following is required to write correct code on the labs. It is here for the curious reader who wants to see where this idea connects to the bigger picture.

The immutability decision carries a consequence that reaches far beyond defensive copying: immutable objects can be shared freely across threads without synchronization. A mutable object handed to two threads is a race condition waiting to happen, because one thread can change the object’s state while the other is reading it. An immutable object has no state to change, so no lock is needed. This is why the modern Java date/time API (LocalDate, Instant, Duration) was designed immutable from the start, while the legacy java.util.Date class is a perennial source of concurrency bugs in older codebases. In professional Java, the choice between immutable and mutable value types is one of the first design decisions made precisely because it determines whether the type can be used freely in concurrent code.

If you want to understand what it takes to build an immutable class from scratch, Bloch’s Effective Java, Item 17 lays out five constraints: no methods that modify state, no mutable state accessible from outside, all fields final, all fields private, and defensive copies of any mutable objects returned from accessors. Those five constraints together form the design pattern for an immutable value object. Every immutable class in the Java standard library (String, BigDecimal, LocalDate) satisfies all five. A class that satisfies them is safe to share, cache, and use as a hash-map key without any of the defensive-copy machinery that mutable classes require.

Source: Bloch, Effective Java, Item 17; Java 25 String API; java.util.Date Javadoc.


Practice

Level 1

For each field in S20.Lab3.Book below, state whether defensive copy is needed: String title, String isbn, Genre type (enum), Publisher pub (mutable class), Author[] authors (mutable class elements), int pages.

Show answer
Field Needs defensive copy?
String title No (immutable)
String isbn No (immutable)
Genre type No (enum, immutable)
Publisher pub Yes (mutable class)
Author[] authors Yes (mutable element type)
int pages No (primitive)

Level 2

A student writes this.name = new String(name) in a constructor for a String name field. Explain the problem and provide the correct form.

Show answer

String is immutable. The caller cannot change the content of a String object through their reference. new String(name) allocates an unnecessary additional String object. Modern lints flag this as a performance issue with no correctness benefit.

Correct form: this.name = name;

Source: Bloch, Effective Java, Item 17; SpotBugs DMSTRINGCTOR.


Level 3

class Order has fields String orderId, LocalDate orderDate, List<Item> lineItems. Which of the three needs a defensive copy? Write the constructor body for the field(s) that require it.

Show answer
Field Copy needed? Reason
String orderId No Immutable
LocalDate orderDate No Immutable (modern date/time)
List<Item> lineItems Yes Mutable; caller can add/remove
public Order(final String orderId, final LocalDate orderDate,
             final List<Item> lineItems)
{
    if (orderId == null || orderId.isEmpty() ||
        orderDate == null || lineItems == null)
        throw new IllegalArgumentException("Bad parameter(s) in Order EVC");
    this.orderId    = orderId;
    this.orderDate  = orderDate;
    this.lineItems  = new ArrayList<>(lineItems);  // defensive copy of mutable list
}

Note: new ArrayList<>(lineItems) copies the list container but still shares the Item references. If Item is mutable, element-by-element copy is needed (see Element-by-Element Array Copy).


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

Which of the following types requires a defensive copy in a constructor?

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

A student writes ‘this.title = new String(title);’ in a constructor where title is a String parameter. What is the best description of that code?

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

A class has the field ‘private final List<String> items;’. After the constructor assigns ‘this.items = items;’, a caller calls items.add(“extra”). What happens?

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

Predict the output of the following. Assume Color is an immutable enum with constants RED, GREEN, BLUE. ``java public class Shirt { private final Color color; public Shirt(Color color) { this.color = color; } public Color getColor() { return color; } } Color c = Color.RED; Shirt s = new Shirt(c); c = Color.BLUE; // caller reassigns their local variable System.out.println(s.getColor()); ``

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

The lesson’s decision rule for constructor parameters is: share immutable, copy mutable. Which of the following applies the rule correctly for all four fields of a class with fields String name, Genre genre (enum), Date createdAt (legacy java.util.Date), int count?

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