Array Copy
Copying an array creates a new array containing the same elements as an existing one. It is needed whenever you want to change data without affecting the original, grow or shrink an array, extract part of it, or protect a class's internal array from outside modification. Java provides several ways to copy arrays, and the most important thing to understand is the difference between copying a reference and copying the contents.
Assignment Does Not Copy
int[] original = {1, 2, 3};
int[] other = original; // copies the reference only
other[0] = 100;
System.out.println(original[0]); // 100
original ──┐
├──► [100, 2, 3]
other ──┘
Both variables point to the same array object, so a change through one is visible through the other. To get an independent array, you must create a new one and copy the elements into it.
Ways to Copy
| Method | Creates a new array? | Can change length? | Typical use |
|---|---|---|---|
arr.clone() | Yes | No | Simple full copy |
Arrays.copyOf(arr, newLength) | Yes | Yes (pads or truncates) | Full copy, growing, shrinking |
Arrays.copyOfRange(arr, from, to) | Yes | Yes (range length) | Copying a slice |
System.arraycopy(src, srcPos, dest, destPos, len) | No (copies into an existing array) | N/A | Copying into a specific position, shifting elements |
| Manual loop | Your choice | Your choice | Copying with transformation |
Arrays.stream(arr).toArray() | Yes | Via filtering | Copy combined with filter/map |
clone()
Every array has a public clone() method that returns a new array of the same type and length:
int[] original = {1, 2, 3};
int[] copy = original.clone();
copy[0] = 100;
System.out.println(Arrays.toString(original)); // [1, 2, 3]
System.out.println(Arrays.toString(copy)); // [100, 2, 3]
For arrays, clone() returns the correct array type directly, so no cast is needed.
Arrays.copyOf
Arrays.copyOf(original, newLength) copies into a new array of the given length:
int[] base = {10, 20, 30};
int[] same = Arrays.copyOf(base, 3); // [10, 20, 30]
int[] longer = Arrays.copyOf(base, 5); // [10, 20, 30, 0, 0]
int[] shorter = Arrays.copyOf(base, 2); // [10, 20]
String[] names = {"Ada", "Linus"};
String[] padded = Arrays.copyOf(names, 4); // [Ada, Linus, null, null]
Extra slots get the type's default value (0, false, or null). This is the standard way to "resize" an array, which in Java always means creating a larger or smaller copy:
int[] data = new int[4];
// ... data is full ...
data = Arrays.copyOf(data, data.length * 2); // the variable now refers to a bigger array
Arrays.copyOfRange
Copies a slice. The start index is inclusive and the end index is exclusive:
char[] letters = {'a', 'b', 'c', 'd', 'e', 'f'};
char[] middle = Arrays.copyOfRange(letters, 2, 5); // [c, d, e]
char[] tail = Arrays.copyOfRange(letters, 4, letters.length); // [e, f]
The length of the result is to - from. If to is beyond the end of the source, the extra positions are filled with defaults. If from is out of range or greater than to, an exception is thrown.
System.arraycopy
System.arraycopy copies elements into an existing destination array, at any position:
System.arraycopy(src, srcPos, dest, destPos, length);
| Parameter | Meaning |
|---|---|
src | Array to copy from |
srcPos | Starting index in the source |
dest | Array to copy into |
destPos | Starting index in the destination |
length | Number of elements to copy |
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[8];
System.arraycopy(src, 1, dest, 3, 3); // copy src[1..3] to dest[3..5]
System.out.println(Arrays.toString(dest)); // [0, 0, 0, 2, 3, 4, 0, 0]
It is implemented natively and is the fastest way to copy elements. Arrays.copyOf and ArrayList use it internally.
System.arraycopy correctly handles overlapping regions within the same array, which makes it ideal for shifting elements:
int[] a = {10, 20, 30, 40, 50};
// shift right by one from index 1, making room at index 1
System.arraycopy(a, 1, a, 2, 3);
a[1] = 15;
System.out.println(Arrays.toString(a)); // [10, 15, 20, 30, 40]
The method behaves as if the source range were first copied to a temporary buffer, so overlapping elements are not overwritten before they are read.
It throws:
IndexOutOfBoundsExceptionif any range falls outside its array,ArrayStoreExceptionif the element types are incompatible (for example, copying anint[]into adouble[], or aStringinto anInteger[]),NullPointerExceptionif either array isnull.
Manual loop
A loop is the most flexible option, because it can transform each element while copying:
double[] pricesInr = {499, 1299, 89.5};
double[] pricesUsd = new double[pricesInr.length];
for (int i = 0; i < pricesInr.length; i++) {
pricesUsd[i] = pricesInr[i] / 83.0;
}
For plain copies, prefer the built-in methods: they are shorter and faster.
Streams
When the copy should also filter or transform, a stream expresses it concisely:
int[] marks = {78, 42, 91, 35, 66};
int[] passing = Arrays.stream(marks).filter(m -> m >= 50).toArray(); // [78, 91, 66]
String[] upper = Arrays.stream(new String[]{"a", "b"})
.map(String::toUpperCase)
.toArray(String[]::new); // [A, B]
toArray(String[]::new) tells the stream which array type to create.
Shallow Copy Versus Deep Copy
All the methods above make shallow copies. For arrays of primitives, that is all you need, since the values themselves are copied. For arrays of objects, only the references are copied; both arrays point to the same objects:
StringBuilder[] original = { new StringBuilder("draft") };
StringBuilder[] copy = original.clone();
copy[0].append(" v2"); // modifies the shared object
System.out.println(original[0]); // draft v2
copy[0] = new StringBuilder("replaced"); // replaces the reference in the copy only
System.out.println(original[0]); // draft v2
original ──► [ ref ] ──┐
├──► StringBuilder("draft v2")
copy ──► [ ref ] ──┘
A deep copy creates new copies of the objects as well:
StringBuilder[] deep = new StringBuilder[original.length];
for (int i = 0; i < original.length; i++) {
deep[i] = new StringBuilder(original[i]); // a new object with the same content
}
Whether a deep copy is needed depends on whether the elements are mutable. Arrays of immutable objects (String, Integer, LocalDate, records with immutable components) can safely be copied shallowly, because the shared objects can never change.
The same applies to multidimensional arrays: int[][].clone() copies the outer array of row references, not the rows themselves. Deep-copy a 2D array by cloning each row.
Defensive Copying
Classes that store or return arrays must copy them to protect their own state. Otherwise, outside code holding a reference can change the class's data behind its back:
public class Exam {
private final int[] scores;
public Exam(int[] scores) {
this.scores = scores.clone(); // copy on the way in
}
public int[] getScores() {
return scores.clone(); // copy on the way out
}
public double average() {
return Arrays.stream(scores).average().orElse(0);
}
}
int[] input = {80, 90, 70};
Exam exam = new Exam(input);
input[0] = 0; // does not affect exam
exam.getScores()[1] = 0; // modifies a copy, not exam's data
System.out.println(exam.average()); // 80.0
Without the two clone() calls, both changes would silently alter the Exam object.
Comparing Copies
After copying, use Arrays.equals (or Arrays.deepEquals for nested arrays) to compare contents. == reports whether two variables refer to the same array:
int[] a = {1, 2, 3};
int[] b = a.clone();
System.out.println(a == b); // false: different arrays
System.out.println(Arrays.equals(a, b)); // true: same contents
Practical Example: A Growable Playlist
The following class stores songs in an array, grows it when full, inserts and removes items by shifting elements, and hands out safe snapshots. This is a simplified version of how ArrayList works internally.
import java.util.Arrays;
public class Playlist {
private String[] songs = new String[2];
private int size = 0;
void add(String song) {
if (size == songs.length) {
songs = Arrays.copyOf(songs, songs.length * 2);
}
songs[size++] = song;
}
void insert(int index, String song) {
if (index < 0 || index > size) throw new IndexOutOfBoundsException(index);
if (size == songs.length) {
songs = Arrays.copyOf(songs, songs.length * 2);
}
System.arraycopy(songs, index, songs, index + 1, size - index);
songs[index] = song;
size++;
}
String remove(int index) {
if (index < 0 || index >= size) throw new IndexOutOfBoundsException(index);
String removed = songs[index];
System.arraycopy(songs, index + 1, songs, index, size - index - 1);
songs[--size] = null;
return removed;
}
String[] snapshot() {
return Arrays.copyOf(songs, size);
}
public static void main(String[] args) {
Playlist p = new Playlist();
p.add("Intro");
p.add("Chorus");
p.add("Bridge");
p.insert(1, "Verse");
String[] before = p.snapshot();
p.remove(0);
p.add("Outro");
System.out.println("Before: " + Arrays.toString(before));
System.out.println("After: " + Arrays.toString(p.snapshot()));
System.out.println("Capacity: " + p.songs.length + ", size: " + p.size);
}
}
Output:
Before: [Intro, Verse, Chorus, Bridge]
After: [Verse, Chorus, Bridge, Outro]
Capacity: 4, size: 4
Each copying tool has a specific role:
Arrays.copyOfto grow. When the array is full, a new one twice the size is created and the old elements are copied in. The thirdaddgrows capacity from 2 to 4.System.arraycopyto make room.insertshifts everything fromindexone place to the right within the same array; the overlapping copy is handled correctly.System.arraycopyto close a gap.removeshifts the following elements left. It then sets the freed last slot tonull, so the removed object can be garbage collected.Arrays.copyOf(songs, size)for snapshots. It returns only the used part of the array as a new array. Thebeforesnapshot is unaffected by later changes, and callers can never modify the playlist's internal array.