☕ Java

Copying Arrays

Copying an array in Java means creating a new array object with the same or a subset of elements from an existing array. Because arrays are objects and array variables hold references, a simple assignment copies only the reference — both variables point to the same array and changes through one are visible through the other. A true copy requires explicitly creating a new array and transferring element values. Java provides multiple mechanisms for this: manual loops, System.arraycopy(), Arrays.copyOf(), Arrays.copyOfRange(), and Object.clone(), each with different trade-offs between conciseness, flexibility, and performance.

Reference Copy vs Element Copy

The most fundamental concept in array copying is the difference between copying a reference and copying the array's contents. An array variable in Java holds a reference — a memory address pointing to the actual array object on the heap. When you write int[] b = a, you copy the reference, not the array. Both a and b now point to the same memory location. Any modification made through b is immediately visible through a, and vice versa, because they are the same array. This aliasing behaviour is intentional and correct in many situations — passing an array to a method that should modify it, storing the same data structure in two different fields, or creating two names for the same collection of values. But when you need an independent copy that can be modified without affecting the original, you must explicitly create a new array and copy the element values. For primitive arrays (int[], double[], boolean[]), copying element by element gives complete independence — primitives are values, and copying a value creates a new independent value. For reference arrays (String[], Object[], custom class arrays), copying element by element creates a shallow copy — the new array contains the same references as the original, pointing to the same objects. Modifying the referenced objects through the copy is visible through the original. A deep copy — creating new copies of all referenced objects recursively — requires additional logic beyond a simple array copy. Understanding this distinction prevents a whole category of subtle bugs where a developer believes they are working with an independent copy but is actually modifying shared data.
Java
// ── Reference copy — same array, two names: ─────────────────────────
int[] original = {1, 2, 3, 4, 5};
int[] alias    = original;      // copies the reference, NOT the data

alias[0] = 99;
System.out.println(original[0]);    // 99 — original changed through alias!
System.out.println(alias == original); // true — same object

// ── True copy — independent new array: ───────────────────────────────
int[] trueCopy = new int[original.length];
for (int i = 0; i < original.length; i++) {
    trueCopy[i] = original[i];
}

trueCopy[0] = 42;
System.out.println(original[0]);  // 99 — original unchanged
System.out.println(trueCopy[0]);  // 42 — only copy changed
System.out.println(trueCopy == original);  // false — different objects

// ── Shallow copy of reference array: ─────────────────────────────────
StringBuilder[] sbArr = {
    new StringBuilder("Hello"),
    new StringBuilder("World")
};
StringBuilder[] shallowCopy = Arrays.copyOf(sbArr, sbArr.length);

// Arrays are independent — adding a new element to one doesn't affect other:
// (would require index assignment — but the referenced OBJECTS are shared)

// Modifying the referenced object through the copy:
shallowCopy[0].append("!!!");
System.out.println(sbArr[0]);       // Hello!!! — affected! same object
System.out.println(shallowCopy[0]); // Hello!!! — same StringBuilder

// ── Deep copy of reference array — copy each object too: ─────────────
StringBuilder[] deepCopy = new StringBuilder[sbArr.length];
for (int i = 0; i < sbArr.length; i++) {
    deepCopy[i] = new StringBuilder(sbArr[i]);  // new StringBuilder from each
}

deepCopy[0].append("---");
System.out.println(sbArr[0]);       // Hello!!! — unchanged
System.out.println(deepCopy[0]);    // Hello!!!--- — only deep copy changed

System.arraycopy() — The Fastest Copy Method

System.arraycopy() is a native method in the JVM that copies elements from a source array to a destination array. It is typically the fastest way to copy array data because the JVM can optimise it as a bulk memory operation — on most JVMs it compiles to a single native instruction or a highly optimised memory copy routine. It is faster than a manual for loop because it avoids Java-level loop overhead and can use hardware-accelerated memory copy instructions. The method signature is System.arraycopy(src, srcPos, dest, destPos, length). All five parameters must be specified — there is no shorthand form. srcPos is the starting index in the source array; destPos is the starting index in the destination array; length is the number of elements to copy. This explicit control makes System.arraycopy() the right choice when copying a portion of one array into a specific position in another, such as merging two arrays, shifting elements within an array, or inserting into the middle. Preconditions are checked but not statically: the destination array must already exist and have enough capacity from destPos to destPos+length; both arrays must be compatible types; and the index arguments must be within bounds. Violations throw ArrayIndexOutOfBoundsException or ArrayStoreException. For overlapping ranges within the same array, System.arraycopy() handles the copy correctly in both directions, making it safe for shifting elements within an array without creating a temporary copy.
Java
// ── System.arraycopy() signature: ────────────────────────────────────
// System.arraycopy(src, srcPos, dest, destPos, length)
//   src     = source array
//   srcPos  = first index to copy FROM in source
//   dest    = destination array (must already exist)
//   destPos = first index to copy INTO in destination
//   length  = number of elements to copy

// ── Basic full array copy: ────────────────────────────────────────────
int[] source = {10, 20, 30, 40, 50};
int[] dest   = new int[source.length];          // must pre-allocate!

System.arraycopy(source, 0, dest, 0, source.length);
System.out.println(Arrays.toString(dest));      // [10, 20, 30, 40, 50]

// ── Copy a range — partial copy: ─────────────────────────────────────
int[] partial = new int[3];
System.arraycopy(source, 1, partial, 0, 3);     // copy indices 1,2,3
System.out.println(Arrays.toString(partial));   // [20, 30, 40]

// ── Copy into an existing array at a specific position: ───────────────
int[] base   = {1, 2, 0, 0, 0, 3, 4};
int[] insert = {7, 8, 9};
System.arraycopy(insert, 0, base, 2, 3);        // insert starting at index 2
System.out.println(Arrays.toString(base));      // [1, 2, 7, 8, 9, 3, 4]

// ── Shift elements right (make room at index 2): ──────────────────────
int[] arr = {10, 20, 30, 40, 50, 0};   // last slot is empty
// Shift elements from index 2 onwards, one position to the right:
System.arraycopy(arr, 2, arr, 3, 3);    // overlapping — safe with arraycopy
arr[2] = 99;
System.out.println(Arrays.toString(arr));  // [10, 20, 99, 30, 40, 50]

// ── Merging two arrays: ───────────────────────────────────────────────
int[] a      = {1, 2, 3};
int[] b      = {4, 5, 6, 7};
int[] merged = new int[a.length + b.length];
System.arraycopy(a, 0, merged, 0,        a.length);
System.arraycopy(b, 0, merged, a.length, b.length);
System.out.println(Arrays.toString(merged));  // [1, 2, 3, 4, 5, 6, 7]

// ── Performance note: ────────────────────────────────────────────────
// System.arraycopy is native and typically 3-10x faster than a for loop
// for large arrays because:
//   1. Runs at native code level — no JVM bytecode interpretation
//   2. Can use CPU's vectorised memory-copy instructions (SIMD)
//   3. JIT compiles to an optimised bulk-copy instruction
// For small arrays (< ~10 elements), the difference is negligible.

Arrays.copyOf() and Arrays.copyOfRange()

Arrays.copyOf() and Arrays.copyOfRange() from java.util.Arrays are higher-level copy methods that create and return a new array. Unlike System.arraycopy(), they handle the destination array allocation internally — you do not need to pre-create the destination. This makes them more concise and less error-prone for the common case of copying an entire array or a contiguous range. Arrays.copyOf(original, newLength) creates a new array of the specified length and copies as many elements from the original as will fit. If newLength is less than the original length, the result is a truncated copy. If newLength is greater, the additional positions are filled with the default value for the array's element type (0 for numeric types, false for boolean, null for reference types). This dual behaviour — truncating or padding — makes Arrays.copyOf() the standard idiom for resizing an array to a new length. Arrays.copyOfRange(original, from, to) copies a half-open range [from, to) into a new array of length to-from. The from index is inclusive; the to index is exclusive. If to exceeds the original array's length, the extra positions are filled with defaults. Both methods preserve the element type — an Arrays.copyOf(intArray, n) returns int[], not Object[]. Internally, both methods use System.arraycopy() for the actual data transfer and are therefore nearly as fast. They are preferred over manual System.arraycopy() when creating a new copy is the goal, because they are more concise and handle the allocation automatically.
Java
// ── Arrays.copyOf() — copy with specified new length: ────────────────
int[] original = {10, 20, 30, 40, 50};

// Copy with same length — exact duplicate:
int[] exact = Arrays.copyOf(original, original.length);
System.out.println(Arrays.toString(exact));    // [10, 20, 30, 40, 50]

// Copy with shorter length — truncation:
int[] shorter = Arrays.copyOf(original, 3);
System.out.println(Arrays.toString(shorter));  // [10, 20, 30]

// Copy with longer length — padded with defaults:
int[] longer = Arrays.copyOf(original, 8);
System.out.println(Arrays.toString(longer));   // [10, 20, 30, 40, 50, 0, 0, 0]

// Reference array — pads with null:
String[] words = {"hello", "world"};
String[] padded = Arrays.copyOf(words, 5);
System.out.println(Arrays.toString(padded));   // [hello, world, null, null, null]

// ── Arrays.copyOfRange() — copy a half-open range [from, to): ─────────
int[] data = {5, 10, 15, 20, 25, 30, 35};

int[] middle = Arrays.copyOfRange(data, 2, 5);  // indices 2,3,4
System.out.println(Arrays.toString(middle));     // [15, 20, 25]

int[] fromStart = Arrays.copyOfRange(data, 0, 4); // indices 0,1,2,3
System.out.println(Arrays.toString(fromStart));   // [5, 10, 15, 20]

int[] toEnd = Arrays.copyOfRange(data, 4, data.length);
System.out.println(Arrays.toString(toEnd));       // [25, 30, 35]

// Range extending beyond array — padded with defaults:
int[] extended = Arrays.copyOfRange(data, 5, 10);
System.out.println(Arrays.toString(extended));    // [30, 35, 0, 0, 0]

// ── Typical use — resizing an array: ──────────────────────────────────
// Manual dynamic array — double capacity when full:
int[] dynamicArr = new int[4];
int size = 0;

// Add elements, resize when full:
for (int i = 0; i < 10; i++) {
    if (size == dynamicArr.length) {
        dynamicArr = Arrays.copyOf(dynamicArr, dynamicArr.length * 2);
    }
    dynamicArr[size++] = (i + 1) * 10;
}
System.out.println(Arrays.toString(Arrays.copyOf(dynamicArr, size)));
// [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

clone() and Copying 2D Arrays

The Object.clone() method is available on arrays and creates a shallow copy of the array. For a 1D primitive array, clone() produces a true independent copy — primitives are values, so copying them is sufficient. For a 1D reference array, clone() copies the references, producing a shallow copy where the new array and the original share the same referenced objects. For 2D and multidimensional arrays, clone() only copies the top-level array. The inner arrays are shared between the original and the clone. This means modifying an inner array through the clone modifies the same inner array that the original references. A true deep copy of a 2D array requires cloning each inner array individually. The clone() approach for 2D arrays is a common source of bugs. Developers call arr.clone() expecting an independent copy and then discover that modifying arr2[0][0] still affects arr[0][0]. The correct approach for a fully independent 2D array copy is either a manual nested loop, or using Arrays.copyOf() on each row within a loop.
Java
// ── clone() on 1D primitive array — true copy: ───────────────────────
int[] original = {1, 2, 3, 4, 5};
int[] cloned   = original.clone();

cloned[0] = 99;
System.out.println(original[0]);  // 1  — unchanged, true copy
System.out.println(cloned[0]);    // 99 — only clone changed

// ── clone() on 1D reference array — SHALLOW copy: ────────────────────
int[][] matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int[][] shallow = matrix.clone();   // only top-level array is cloned!

// Outer arrays are independent:
shallow[0] = new int[]{10, 11, 12};  // replaces row 0 in shallow
System.out.println(Arrays.toString(matrix[0]));   // [1, 2, 3] — unchanged
System.out.println(Arrays.toString(shallow[0]));  // [10, 11, 12]

// BUT inner arrays are shared — modifying an inner array affects both:
shallow[1][0] = 99;   // modifies shared inner array
System.out.println(matrix[1][0]);   // 99 — AFFECTED! shared object
System.out.println(shallow[1][0]);  // 99

// ── True deep copy of 2D array: ───────────────────────────────────────
int[][] original2D = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int[][] deepCopy2D = new int[original2D.length][];

for (int i = 0; i < original2D.length; i++) {
    deepCopy2D[i] = original2D[i].clone();  // clone each inner array
    // or: deepCopy2D[i] = Arrays.copyOf(original2D[i], original2D[i].length);
}

deepCopy2D[1][0] = 99;
System.out.println(original2D[1][0]);  // 4  — unchanged, truly independent
System.out.println(deepCopy2D[1][0]);  // 99

// ── Summary of copy methods: ──────────────────────────────────────────
//
// Method              Allocates   Partial   Speed     Notes
// ─────────────────── ─────────── ──────────────────  ─────────────────
// for loop            Manual      ✓ (manual) Moderate Full control
// System.arraycopy()  Manual      ✓          Fastest   Most flexible
// Arrays.copyOf()     Auto        Truncate/  Fast      Simplest for full
//                                 pad only            copy or resize
// Arrays.copyOfRange()Auto        ✓          Fast      Clean range copy
// .clone()            Auto        ✗          Fast      Shallow for refs

Related Topics in Arrays

Array Basics
An array in Java is a fixed-size, ordered collection of elements of the same type stored in a contiguous block of memory. Once created, the size of an array cannot change. Arrays are the simplest and most fundamental data structure in Java — they underlie many higher-level collections and provide direct, indexed access to elements in constant time O(1). Every array in Java is an object, inherits from java.lang.Object, and can hold either primitives or references.
One-Dimensional Array
A one-dimensional array is a linear sequence of elements of the same type, accessed by a single integer index. It is the simplest array form in Java and the foundation for understanding multidimensional arrays and collection classes. One-dimensional arrays are used to store and process lists of values — student grades, product prices, daily temperatures, character sequences — any time you need to work with a fixed-size ordered collection of homogeneous data.
Two-Dimensional Array
A two-dimensional array in Java is an array of arrays — each element of the outer array is itself a one-dimensional array. It is used to represent tabular data with rows and columns: matrices, game boards, spreadsheet grids, pixel buffers, and any data that is naturally organised in two dimensions. In Java, a 2D array is declared with two sets of brackets and accessed with two indices: array[row][column].
Multidimensional Array
A multidimensional array in Java is an array with more than two dimensions — an array of arrays of arrays, and so on. While two-dimensional arrays suffice for most programming tasks, three-dimensional arrays are used for spatial data (x, y, z coordinates of a 3D grid), volumetric data (layers, rows, columns), RGB pixel buffers, and scientific computations involving tensors. Java supports arbitrary dimensionality, though arrays beyond three dimensions are rare in practice.