Saltar al contenido principal

Clonación de arrays

Gestión de arrays en memoria

Para entender por qué la clonación de arrays requiere especial atención, primero hay que comprender cómo Java gestiona los arrays en memoria.

En Java existen dos zonas de memoria principales:

  • Stack (pila): almacena las variables locales y las referencias a objetos.
  • Heap (montón): almacena los objetos en sí (el contenido), incluidos los arrays.

Cuando declaras un array, la variable no contiene el array directamente, sino una referencia (la dirección de memoria) que apunta al array real, que vive en el heap:

Stack                  Heap
┌─────────┐ ┌─────────────────────┐
│ numeros │ ────────▶ │ [ 1, 2, 3, 4, 5 ] │
└─────────┘ └─────────────────────┘

Esto tiene una consecuencia importante: si asignas un array a otra variable con =, no estás copiando el array, sino copiando la referencia. Ambas variables apuntan al mismo objeto en el heap:

int[] original = {1, 2, 3};
int[] copia = original; // copia la referencia, NO el array

copia[0] = 99;
System.out.println(original[0]); // 99 (el original también cambia)
Stack                  Heap
┌──────────┐ ┌──────────────────┐
│ original │ ────────▶│ [ 99, 2, 3 ] │
└──────────┘ ┌───▶└──────────────────┘
┌──────────┐ │
│ copia │ ────┘
└──────────┘

Esto ocurre con cualquier objeto en Java, y los arrays no son una excepción. Para obtener una copia independiente hay que realizar una clonación.

Arrays unidimensionales

Un array 1D contiene valores primitivos (como int, double) u objetos directamente en sus posiciones. Al clonarlo, basta con copiar esos valores a un nuevo array.

Copia superficial vs. copia profunda

Cuando los elementos del array son tipos primitivos, cualquier método de copia produce una copia totalmente independiente (los primitivos se copian por valor). Cuando los elementos son objetos, la situación cambia: se copia la referencia al objeto, no el objeto en sí. Esto se llama copia superficial (shallow copy).

Para arrays 1D de primitivos, todos los métodos siguientes son equivalentes y suficientes.

Método 1: clone()

El método clone() está disponible en todos los arrays de Java y devuelve una copia del array.

int[] original = {1, 2, 3, 4, 5};
int[] copia = original.clone();

copia[0] = 99;
System.out.println(original[0]); // 1 — el original no se ve afectado
System.out.println(copia[0]); // 99

Método 2: Arrays.copyOf()

Permite copiar un array especificando la longitud de la copia. Si la longitud es mayor que el original, los elementos extra se rellenan con el valor por defecto del tipo.

import java.util.Arrays;

int[] original = {1, 2, 3, 4, 5};

int[] copiaExacta = Arrays.copyOf(original, original.length); // copia completa
int[] copiaMayor = Arrays.copyOf(original, 8); // [1, 2, 3, 4, 5, 0, 0, 0]
int[] copiaMenor = Arrays.copyOf(original, 3); // [1, 2, 3]

Método 3: Arrays.copyOfRange()

Copia un rango específico del array original (el índice final es exclusivo).

int[] original = {10, 20, 30, 40, 50};
int[] rango = Arrays.copyOfRange(original, 1, 4); // [20, 30, 40]

Método 4: System.arraycopy()

Método de bajo nivel, más verboso pero el más eficiente. Copia elementos de un array origen a uno destino.

int[] original = {1, 2, 3, 4, 5};
int[] destino = new int[original.length];

// System.arraycopy(origen, posOrigen, destino, posDestino, numElementos)
System.arraycopy(original, 0, destino, 0, original.length);

destino[0] = 99;
System.out.println(original[0]); // 1
System.out.println(destino[0]); // 99

Arrays bidimensionales

Aquí la situación se complica. Un array 2D en Java es un array de referencias, donde cada posición apunta a un array 1D independiente en el heap:

Stack              Heap
┌────────┐ ┌────────────────────────────────────────────┐
│ matriz │───────▶│ [ ref0, ref1, ref2 ] │
└────────┘ │ │ │ │ │
│ ▼ ▼ ▼ │
│ [1,2,3] [4,5,6] [7,8,9] │
└────────────────────────────────────────────┘

El problema: clone() hace una copia superficial

Si usas clone() en un array 2D, se crea un nuevo array externo, pero las referencias internas siguen apuntando a los mismos arrays de filas:

int[][] original = {{1, 2, 3}, {4, 5, 6}};
int[][] copia = original.clone(); // copia superficial

copia[0][0] = 99;
System.out.println(original[0][0]); // 99 — ¡el original cambia!
Stack               Heap
┌──────────┐ ┌──────────────────┐
│ original │──────▶│ [ ref0, ref1 ] │
└──────────┘ └────┬──────┬──────┘
│ │
┌──────────┐ ┌────▼──────▼──────┐ ← ambas matrices
│ copia │──────▶│ [ ref0, ref1 ] │ comparten las filas
└──────────┘ └──────────────────┘
↓ ↓
[99,2,3] [4,5,6]

La solución: copia profunda manual

Para conseguir una copia completamente independiente hay que clonar el array externo y también cada fila:

int[][] original = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int[][] copia = new int[original.length][];

for (int i = 0; i < original.length; i++) {
copia[i] = original[i].clone(); // clonar cada fila individualmente
}

copia[0][0] = 99;
System.out.println(original[0][0]); // 1 — ahora el original está protegido
System.out.println(copia[0][0]); // 99

También se puede usar Arrays.copyOf() para cada fila con el mismo resultado:

for (int i = 0; i < original.length; i++) {
copia[i] = Arrays.copyOf(original[i], original[i].length);
}

Arrays tridimensionales

La misma lógica se extiende una capa más. Un array 3D es un array de referencias a arrays 2D, que a su vez son arrays de referencias a arrays 1D:

matriz3D ──▶ [ ref_capa0, ref_capa1 ]
│ │
▼ ▼
[ref0, ref1] [ref2, ref3]
│ │ │ │
▼ ▼ ▼ ▼
[1,2] [3,4] [5,6] [7,8]

Usando clone() o cualquier copia superficial solo se copiaría el nivel más externo; los niveles intermedios y las filas seguirían compartidos.

Copia profunda: tres niveles de bucles anidados

La solución es recorrer todos los niveles y clonar cada array en cada nivel:

int[][][] original = {
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}}
};

int[][][] copia = new int[original.length][][];

for (int k = 0; k < original.length; k++) {
copia[k] = new int[original[k].length][];

for (int i = 0; i < original[k].length; i++) {
copia[k][i] = original[k][i].clone(); // clonar cada fila
}
}

copia[0][0][0] = 99;
System.out.println(original[0][0][0]); // 1 — el original no se ve afectado
System.out.println(copia[0][0][0]); // 99