JAVA / LOOPS AND ARRAYS
Copying arrays and the shared-reference trap
Tell an alias from a real array copy, build independent copies with copyOf, clone and System.arraycopy, and deep-copy nested arrays row by row.
What you will learn
- Predict which writes are visible through both names after int[] b = a;
- Copy a primitive array with Arrays.copyOf, clone() or System.arraycopy
- Deep-copy an int[][] row by row instead of trusting a one-level copy
- Use Arrays.equals for content and == only when you mean identity
Understanding Copying arrays and the shared-reference trap
An array variable does not contain the elements; it contains a reference to the block of memory that holds them. So int[] b = a; copies that single reference and nothing else: afterwards a and b are two names for one block, b.length and a.length describe the same block, and b[0] = 99 is immediately visible through a. Nothing in the syntax hints at this, which is why the line reads like a copy and behaves like a second label on the same box.
To get an independent second block you have to ask for one. Arrays.copyOf(a, a.length) allocates a new array and fills it from a, a.clone() does the same with a shorter spelling, System.arraycopy(a, 0, b, 0, n) fills an array you already allocated, and a plain for loop does it by hand. For an int[] all four give total independence, because each slot holds a value that is duplicated when it is copied. Arrays.copyOf also accepts a length different from the source, truncating or padding with 0, and that is exactly how a fixed-length array gets "grown": you build a longer one and copy into it.
When the slots hold references rather than values, a one-level copy duplicates the slots and leaves their targets shared. int[][] copy = Arrays.copyOf(grid, grid.length) gives you an outer array you own, so copy[0] = newRow cannot disturb grid, but copy[0][0] = 9 writes into the row that both outer arrays point at. The cure is to copy every level you intend to write through: allocate the outer array, then copy each row into it. Count how many arrows a write has to follow, because a copy protects only the level it actually duplicated.
import java.util.Arrays;
public class ArrayCopy {
public static void main(String[] args) {
int[] original = {3, 1, 4, 1, 5};
int[] alias = original; // one array, two names
int[] copy = Arrays.copyOf(original, original.length); // a genuinely new array
alias[0] = 99;
copy[1] = 77;
System.out.println("original: " + Arrays.toString(original));
System.out.println("alias: " + Arrays.toString(alias));
System.out.println("copy: " + Arrays.toString(copy));
int[] fresh = original.clone();
System.out.println("original == alias: " + (original == alias));
System.out.println("original == fresh: " + (original == fresh));
System.out.println("Arrays.equals(original, fresh): " + Arrays.equals(original, fresh));
}
}Assignment copies the reference, so an independent array exists only if you allocate one, and only for the levels you copied.
Worked examples
One level deep is not deep enough
Shows that copying an int[][] with Arrays.copyOf duplicates the outer slots but shares the rows, and how a per-row copy fixes it.
import java.util.Arrays;
public class RowSharing {
public static void main(String[] args) {
int[][] grid = {{1, 2}, {3, 4}};
int[][] shallow = Arrays.copyOf(grid, grid.length);
shallow[0][0] = 50; // writes through a shared row
shallow[1] = new int[]{9, 9}; // replaces a slot only in shallow
int[][] deep = new int[grid.length][];
for (int r = 0; r < grid.length; r++) {
deep[r] = Arrays.copyOf(grid[r], grid[r].length);
}
deep[0][1] = 60;
System.out.println("grid: " + Arrays.deepToString(grid));
System.out.println("shallow: " + Arrays.deepToString(shallow));
System.out.println("deep: " + Arrays.deepToString(deep));
}
}Example explained
Line 1Arrays.copyOf(grid, grid.length) allocates a new outer array of length 2 and copies the two row references into it.
Line 2shallow[0][0] = 50 follows one of those shared references, so the write lands in the single row object that grid[0] also names.
Line 3shallow[1] = new int[]{9, 9} overwrites a slot in shallow's own outer array, which is why grid[1] still shows [3, 4].
Line 4deep[r] = Arrays.copyOf(grid[r], ...) gives every row its own block, so deep[0][1] = 60 cannot reach grid.
What a method can and cannot change
Demonstrates that a method can mutate the caller's array through the reference it receives, but reassigning the parameter changes nothing outside.
import java.util.Arrays;
public class PassingArrays {
static void scaleInPlace(int[] values) {
for (int i = 0; i < values.length; i++) {
values[i] *= 2;
}
}
static void replaceParameter(int[] values) {
values = new int[]{-1, -1, -1};
values[0] = 100;
}
public static void main(String[] args) {
int[] data = {1, 2, 3};
scaleInPlace(data);
System.out.println("after scaleInPlace: " + Arrays.toString(data));
replaceParameter(data);
System.out.println("after replaceParameter: " + Arrays.toString(data));
int[] safe = data.clone();
scaleInPlace(safe);
System.out.println("data: " + Arrays.toString(data) + " safe: " + Arrays.toString(safe));
}
}Example explained
Line 1scaleInPlace gets its own copy of the reference, but that reference still points at the caller's block, so values[i] *= 2 rewrites data.
Line 2replaceParameter reassigns its local variable, cutting its link to data, so the following write goes into a throwaway array.
Line 3data.clone() allocates a separate block, so the second scaleInPlace call doubles only safe and leaves data at [2, 4, 6].
Shifting and resizing with the copy helpers
Uses System.arraycopy on a single array to open a gap, then copyOfRange and copyOf to extract and pad regions.
import java.util.Arrays;
public class ShiftAndResize {
public static void main(String[] args) {
int[] letters = {10, 20, 30, 40, 0};
System.arraycopy(letters, 1, letters, 2, 3); // shift 3 elements one slot right
letters[1] = 15;
System.out.println(Arrays.toString(letters));
int[] middle = Arrays.copyOfRange(letters, 1, 4);
System.out.println(Arrays.toString(middle));
int[] padded = Arrays.copyOf(middle, 5);
System.out.println(Arrays.toString(padded));
}
}Example explained
Line 1System.arraycopy(letters, 1, letters, 2, 3) copies indices 1..3 into indices 2..4 of the same array; the source region is treated as if buffered first, so the overlap does not smear values.
Line 2Index 1 still holds the old 20 after the shift, which is why letters[1] = 15 is needed to fill the gap.
Line 3Arrays.copyOfRange(letters, 1, 4) returns indices 1, 2 and 3 because the upper bound is exclusive.
Line 4Arrays.copyOf(middle, 5) allocates a longer array and leaves the two extra int slots at their default 0.
Important notes
clone() on an array needs no cast and never throws CloneNotSupportedException, but on an int[][] it is exactly as shallow as Arrays.copyOf; the Arrays class offers deepToString and deepEquals, not a deep copy.
A shallow copy of a String[] is harmless in practice because String objects cannot be mutated; sharing only bites when the shared elements are themselves mutable.
Common mistakes
Writing int[] backup = data; before Arrays.sort(data): both names refer to one array, so backup comes out sorted too and the original order is gone for good.
Calling Arrays.copyOf or clone() on an int[][] and then writing copy[0][0] = 9: the rows are still shared, so the original grid changes and the bug shows up far from the copy.
Using copy == original as a content check: it asks whether they are the same object, so it reports false for a perfectly correct copy and true only for an alias.
Try it yourself
Change, predict, then run
Start from int[] scores = {5, 3, 9, 1};, produce a sorted array while scores keeps its original order, and print both with Arrays.toString to prove it. Then break it on purpose by replacing the copy with a plain assignment and compare the two outputs.
Open the Java workspaceCheck your understanding
Given int[][] a = {{1, 2}, {3, 4}}; int[][] b = a.clone(); b[0][0] = 9; b[1] = new int[]{7, 7}; what does Arrays.deepToString(a) print?
- [[9, 2], [3, 4]]
- [[1, 2], [3, 4]]
- [[9, 2], [7, 7]]
- [[1, 2], [7, 7]]
Show answer
clone() duplicates only the outer array's two slots, and both copies' slots point at the same two row arrays, so b[0][0] = 9 writes into the row that a[0] also refers to. Option 2 is tempting because it treats b[1] = new int[]{7, 7} as another shared write, but that statement only overwrites a slot in b's private outer array, leaving a[1] pointing at the old {3, 4}; option 1 would require clone() to be a deep copy, which it is not.