← Skill tree CS Skill Tree 0 CSCD211

Element-by-Element Array 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. Arrays.copyOf. int[] copy = Arrays.copyOf(original, original.length); What is the relationship between copy[0] and original[0] after this call?

Check

For a primitive array: copy[0] is a separate copy of the integer value; changing copy[0] does not change original[0]. For a reference array: copy[0] and original[0] still point to the same object; they are different array slots holding the same reference.

2. Shallow vs. deep. What does “shallow copy” mean for a Author[] array?

Check

A shallow copy creates a new array but fills it with the same Author references. Modifying an Author object through either array affects the same object. A deep copy creates new Author objects at each position; the arrays are completely independent.

3. Why the outer copy alone is insufficient. If Author is a mutable class and you do this.authors = Arrays.copyOf(author, author.length), can the caller mutate the Book’s authors?

Check

Yes, through the Author objects themselves. author[0].setFirstName("Hacked") changes the first author’s name in both the original array and the copied array, because both hold references to the same Author instance.


Try This First

The S20.Lab3.Book constructor receives an Author[] author parameter. The Book’s Javadoc says: “NOTE You will not just assign the passed in author array to the class level variable”. Before reading: why is this warning necessary? What would happen if the book did this.authors = author?

Check

this.authors = author stores the same array reference. The caller could later do author[0] = new Author("Hacked", "Author"), replacing the reference in the shared array. The Book’s this.authors[0] would also change, because they are the same array. The Book would have no control over the identity of its own authors.


What You Need To Walk In With

The Insight: Defensive copy for arrays of mutable objects requires two levels: a fresh outer array AND fresh object instances at each position. Copying only the outer array (Arrays.copyOf) leaves the element references shared. This is called a shallow copy, and it only protects against someone replacing a slot (author[0] = x), not against someone mutating the object in a slot (author[0].setFirstName("x")). When elements are mutable, only a deep (element-by-element) copy provides true isolation.

If two levels of copying feels complex, break the work into two separate tasks. Task 1 allocates a fresh outer array. Task 2 loops over each position and allocates a new element object. Each task is a single line or a simple loop, and writing them separately makes each one easy to verify independently.

You can: explain why Arrays.copyOf is insufficient for mutable elements; write the two-step element-by-element copy; apply it correctly to S20.Lab3.Book’s Author[] field; and write the matching defensive copy in an accessor that returns the same field.


How It Works

The S20 Lab 3 pattern

From S20.Lab3.Book.java, the constructor body for Author[] authors:

this.authors = new Author[author.length];   // Step 1: fresh outer array
for (int x = 0; x < author.length; x++)    // Step 2: fresh element at each index
{
    String[] name = author[x].getName().split(",");
    String first  = name.length > 1 ? name[1].trim() : "";
    String last   = name[0].trim();
    this.authors[x] = new Author(first, last);   // new Author object
}

After this runs:

Why Arrays.copyOf is not enough

// INSUFFICIENT: shallow copy
this.authors = Arrays.copyOf(author, author.length);

// After this:
// this.authors[0] == author[0]  (same Author object!)
// author[0].setFirstName("Hacked")  -> also changes this.authors[0]

Arrays.copyOf creates a new array of the same length and copies the references. The elements (the Author objects) are not copied. Both arrays point to the same objects.

For immutable elements (String[], Integer[]), this is sufficient because the elements cannot be mutated. For mutable elements, it is not.

The defensive copy in the accessor

Bloch, Effective Java, Item 50 identifies a second location where defensive copy is needed: the return path. If getAuthors() returns this.authors directly, the caller receives a reference to the internal array. Mutations through that reference affect the Book’s internal state.

// WRONG: exposes internal array
public Author[] getAuthors() {
    return this.authors;
}

// CORRECT: defensive copy on the way out
public Author[] getAuthors() {
    Author[] copy = new Author[this.authors.length];
    for (int x = 0; x < this.authors.length; x++)
        copy[x] = new Author(this.authors[x].getFirstName(),
                             this.authors[x].getLastName());
    return copy;
}

From W17.Lab1.Sandwich.getToppings(): the same element-by-element copy is applied to String[] on both the constructor (copy in) and the accessor (copy out). String is immutable, so a shallow copy would suffice here, but the pattern is correct regardless.


Quick check

Check your understanding

In the S20.Lab3.Book constructor, why must the loop call new Author(...) at each index rather than just assigning author[x] to this.authors[x]?

Tier 2 · BJP (Reges and Stepp), Ch 8; S20.Lab3.Book.java Javadoc


Worked Example: Predict Then Check

Author[] original = { new Author("Alice", "Smith"), new Author("Bob", "Jones") };

// Book A: shallow copy
// Book B: deep (element-by-element) copy

original[0].setFirstName("Hacked");

System.out.println("A[0]: " + bookA.getAuthors()[0].getFirstName());
System.out.println("B[0]: " + bookB.getAuthors()[0].getFirstName());

Predict the output for each book (assuming both getAuthors return the internal array directly for simplicity).

Reasoning

Book A used shallow copy: this.authors[0] is the same object as original[0]. After setFirstName("Hacked"), the name changed in the shared object.

Book B used element-by-element copy: this.authors[0] is a different Author object initialized from original[0]’s data at construction time. The mutation to original[0] does not reach bookB.authors[0].

Show answer

A: "Hacked" (shared element, mutation visible) B: "Alice" (independent copy, mutation not visible)


Common Misconceptions

Misconception 1: Arrays.copyOf is sufficient defensive copy for mutable elements

Wrong mental model:Arrays.copyOf(author, author.length) creates a fresh copy of the array; the Book is isolated.”

Why it breaks: Arrays.copyOf copies only the array container (the slots), not the objects in the slots. For mutable element types, the caller can still mutate the shared element objects. The Book’s internal state changes without any assignment through the internal reference.

How to correct: For an array of mutable elements, loop and call new Element(...) at each index. This is the element-by-element deep copy.

Source: Bloch, Effective Java, Item 50; S20.Lab3.Book.java Javadoc warning.

Quick check

Check your understanding

A Book constructor contains: this.authors = Arrays.copyOf(author, author.length); Later, the caller calls author[0].setFirstName(“Hacked”). What happens to the Book’s first author’s name?

Tier 1 · Bloch, Effective Java, Item 50; BJP (Reges and Stepp), Ch 8


Misconception 2: defensive copy is only needed in the constructor

Wrong mental model: “I copied on the way in; the accessor can just return this.authors directly.”

Why it breaks: The accessor exposes the internal array. The caller can store the returned reference and mutate elements or replace slots:

Author[] exposed = book.getAuthors();
exposed[0].setFirstName("Hacked");   // affects book's internal state

How to correct: Apply defensive copy in the accessor too. Return a fresh copy of the array with fresh element copies. Alternatively, expose only indexed access (getAuthor(int i)) with no way to obtain the array reference.

Source: Bloch, Effective Java, Item 50.


Formal Definition and Interface Contract

From Bloch, Effective Java, Item 50:

If a class contains components that are mutable, you must defend against this mutation by making a defensive copy. [...] Note also that we did not use clone to make a defensive copy of a parameter whose type is a subclassable class, because we cannot guarantee that the clone method returns an object whose class is java.util.Date and not some untrusted subclass. [...] When in doubt, don’t use clone.

From S20.Lab3.Book.java Javadoc:

NOTE You will not just assign the passed in author array to the class level variable.


Mental Model

Think of a shallow copy as photocopying a phone directory. You get a new set of pages with the same phone numbers listed. If someone changes their phone number in the original directory, the copies’ entries still show the old number’s value... but wait: the “phone numbers” here are references to people (mutable objects). Changing a person’s name changes it through every directory that refers to them. A deep copy would also copy the people themselves, so each directory has its own independent people.


Connections

Within CSCD 210/211: S20.Lab3.Book and W17.Lab1.Sandwich both demonstrate the pattern. Sandwich.getToppings() applies element-by-element copy on the read path; Book’s constructor applies it on the write path.

Looking back: Immutable Fields Need No Defensive Copy covered when NOT to copy. This lesson covers the deepest form of copying: when the element type itself is mutable.

Looking ahead: The next topic covers multiple constructors. A class with arrays of mutable objects needs the same defensive copy pattern in each constructor that accepts the array.


Practice

Level 1

Write the element-by-element defensive copy for Author[] author into this.authors, given that Author has constructors Author(String firstName, String lastName) and getters getFirstName(), getLastName().

Show answer
this.authors = new Author[author.length];
for (int x = 0; x < author.length; x++)
    this.authors[x] = new Author(author[x].getFirstName(), author[x].getLastName());

Pattern from S20.Lab3.Book.java.


Level 2

Explain why this.authors = Arrays.copyOf(author, author.length) is insufficient and this.authors = author is wrong. Then write the correct form.

Show answer

this.authors = author: stores the same array reference. Any slot replacement (author[0] = x) in the caller’s array is visible through this.authors.

this.authors = Arrays.copyOf(author, author.length): creates a new array but copies element references. Any mutation of an Author object (author[0].setFirstName("x")) is visible through this.authors[0] because they reference the same Author.

Correct form (element-by-element):

this.authors = new Author[author.length];
for (int x = 0; x < author.length; x++)
    this.authors[x] = new Author(author[x].getFirstName(), author[x].getLastName());

Level 3

Write public Author[] getAuthors() as a defensive copy accessor for the this.authors field. Explain why this accessor copy is also necessary.

Show answer
public Author[] getAuthors()
{
    Author[] copy = new Author[this.authors.length];
    for (int x = 0; x < this.authors.length; x++)
        copy[x] = new Author(this.authors[x].getFirstName(),
                             this.authors[x].getLastName());
    return copy;
}

Why it is necessary: without the accessor copy, the caller receives a reference to the Book’s internal this.authors array. The caller can then replace a slot (returned[0] = new Author("Hacked", "Author")) or mutate an element (returned[0].setFirstName("Hacked")). Both changes would affect the Book’s internal state. The accessor copy ensures the caller receives an independent array with independent element objects, so mutations to the returned value cannot reach the Book’s internals.

Source: Bloch, Effective Java, Item 50; W17.Lab1.Sandwich.getToppings().


Go Deeper (optional)

Nothing in this section is needed to write correct code. It is here for curiosity, and you should feel good about the topic whether or not you read it.

Where this shows up in professional code. Shallow vs. deep copy is a standard technical interview topic and a recurring source of production bugs in real codebases. The professional pattern matches exactly what the lab teaches: apply defensive copy at both the write path (constructor) and the read path (accessor), and for every mutable type held in a collection or array. Missing the accessor copy is the more common omission in professional code because it is the less obvious of the two.

When the manual loop is not enough. Java provides Arrays.copyOf for shallow copy and nothing built-in for deep copy of objects. For simple flat classes like Author, the manual loop is correct and readable. For complex object graphs where each element itself holds mutable arrays or references, a single loop is insufficient: you need a recursive strategy or a serialization-based approach. Libraries such as Apache Commons Lang provide SerializationUtils.clone for this, but they require every class in the graph to implement Serializable and carry significant overhead. The manual loop in the lab is appropriate precisely because Author is flat: its fields are strings, and strings are immutable, so one level of element-by-element copy closes all the gaps.

The design alternative. One reason this two-level copy feels laborious is that Author is a mutable class. If Author were designed as immutable (all fields final, no setters), the problem disappears entirely: you would never need to deep-copy it, because no caller can mutate an Author object through any reference. This trade-off between designing types to be immutable versus writing defensive copy everywhere they appear is central to how the Java standard library and libraries like Guava approach API design. Bloch addresses this directly in Effective Java, Item 17 (“Minimize mutability”), arguing that immutable value types should be the default and mutable types the deliberate exception. The defensive-copy pattern you are practicing here is the correct answer when you inherit a mutable type you did not design.


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

Why is Arrays.copyOf insufficient when the array holds mutable objects?

Tier 1 · BJP (Reges and Stepp), Ch 8; Bloch, Effective Java, Item 50

In S20.Lab3.Book, the constructor for Author[] does which two things to achieve a true defensive copy?

Tier 1 · BJP (Reges and Stepp), Ch 8; S20.Lab3.Book.java Javadoc

A getAuthors() accessor returns this.authors directly without copying. Which caller action corrupts the Book’s internal state?

Tier 2 · Bloch, Effective Java, Item 50

Trace this code. What does it print for authorName? Author a = new Author(“Alice”, “Smith”); Author[] arr = { a }; Author[] shallow = Arrays.copyOf(arr, arr.length); a.setFirstName(“Changed”); String authorName = shallow[0].getFirstName(); System.out.println(authorName);

Tier 2 · BJP (Reges and Stepp), Ch 8; Bloch, Effective Java, Item 50

W17.Lab1.Sandwich.getToppings() applies element-by-element copy on String[] toppings. Bloch (Effective Java, Item 50) identifies this as defensive copy on the read path. Which statement best explains when shallow copy would actually be sufficient for String[]?

Tier 3 · BJP (Reges and Stepp), Ch 8; Bloch, Effective Java, Item 50; W17.Lab1.Sandwich.getToppings()