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