W3docs

Clonación de objetos en Java

Copia objetos Java con clone(), la interfaz Cloneable y las diferencias entre copias superficiales y profundas.

Clonar un objeto significa producir un nuevo objeto con el mismo estado: una copia que puedes mutar de forma independiente al original. La solución integrada de Java es Object.clone() junto con la interfaz marcadora Cloneable, pero el diseño tiene suficientes aristas que la mayor parte del código moderno recurre a un constructor de copia o a un método de fábrica. Este capítulo muestra ambas rutas y la trampa que existe entre ellas.

La ruta integrada: Object.clone()

Object.clone() es protected y realiza una copia superficial campo a campo de la instancia. Para usarlo debes:

  1. Hacer que tu clase implemente Cloneable — una interfaz marcadora sin métodos. Sin ella, clone() lanza CloneNotSupportedException.
  2. Sobrescribir clone() para hacerlo public y (generalmente) estrechar el tipo de retorno a la clase real.
public class Box implements Cloneable {
  int size;

  @Override
  public Box clone() {
    try {
      return (Box) super.clone();
    } catch (CloneNotSupportedException e) {
      throw new AssertionError(e);   // can't happen — we implement Cloneable
    }
  }
}

super.clone() produce la copia real. El try/catch es burocracia: la excepción comprobada está declarada en Object.clone, pero nuestra clase implementa Cloneable, por lo que la excepción es inalcanzable.

Copia superficial: qué hace realmente

Una copia superficial duplica los campos inmediatos. Las referencias dentro del objeto se copian como referencias, no como nuevos objetos — así que el original y el clon comparten aquello a lo que apuntan esas referencias:

public class Person implements Cloneable {
  String name;
  int[]  scores;

  @Override
  public Person clone() {
    try { return (Person) super.clone(); }
    catch (CloneNotSupportedException e) { throw new AssertionError(e); }
  }
}

Person a = new Person();
a.scores = new int[]{1, 2, 3};
Person b = a.clone();
b.scores[0] = 99;
System.out.println(a.scores[0]);   // 99 — they share the same array

Para primitivos y valores inmutables (String, Integer, LocalDate), la copia superficial está bien. Para subobjetos mutables, casi siempre es incorrecta: mutar el clon repercute en el original.

Copia profunda: la solución

Para obtener una copia verdaderamente independiente, sobrescribe clone() para copiar recursivamente los campos mutables:

@Override
public Person clone() {
  try {
    Person copy   = (Person) super.clone();
    copy.scores   = scores.clone();          // arrays have their own clone()
    return copy;
  } catch (CloneNotSupportedException e) {
    throw new AssertionError(e);
  }
}

Los arrays implementan Cloneable de forma nativa y se copian con .clone(). Las colecciones no: para un campo List<String> escribirías copy.items = new ArrayList<>(items); (ver ArrayList). Para un grafo de tus propios tipos mutables, cada tipo del grafo necesita participar.

Por qué Cloneable tiene mala reputación

Algunos inconvenientes hacen que clone() sea incómodo:

  • Es una interfaz marcadora sin método clone() — el propio Cloneable no expone nada; el contrato reside en Object.clone().
  • Omite los constructores — los campos del nuevo objeto los rellena la JVM, por lo que los invariantes que aplicas en tu constructor no se vuelven a comprobar.
  • Las subclases heredan la obligación: si Parent sobrescribe clone(), cada subclase debe mantener sincronizada la lógica de copia profunda o heredar silenciosamente una versión superficial rota.
  • La excepción comprobada, el cast, la llamada a super.clone() — cada sobrescritura repite el mismo ruido.

La alternativa moderna: constructor de copia

Un constructor de copia es simplemente un constructor que recibe una instancia de la misma clase y copia sus campos:

public class Person {
  String name;
  int[]  scores;

  public Person(Person other) {
    this.name   = other.name;
    this.scores = other.scores.clone();   // deep where it matters
  }
}

Person b = new Person(a);

Se ejecuta a través del constructor normal, por lo que los invariantes se comprueban. Es Java puro — sin interfaz marcadora, sin CloneNotSupportedException, sin cast. Las subclases simplemente escriben su propio constructor de copia que llama a super(other). La recomendación de Effective Java es preferir constructores de copia (o fábricas estáticas copyOf) antes que clone.

Las clases similares a colecciones ya siguen este patrón: new ArrayList<>(other), new HashMap<>(other), Set.copyOf(other).

¿Qué enfoque debo usar?

SituaciónEnfoque recomendado
Clase nueva y simple que controlasConstructor de copia o fábrica estática copyOf
Clase con solo campos primitivos / inmutablesCualquiera — incluso un clone() superficial es seguro
Clase con campos mutables (listas, arrays, objetos anidados)Constructor de copia con copias profundas explícitas
API existente que ya requiere CloneableSobrescribir clone() y copiar profundamente los campos mutables
Tipo de valor que puedes rediseñarHazlo inmutable — entonces no se necesita ninguna copia

La versión corta: prefiere un constructor de copia para código nuevo y recurre a clone() solo cuando un contrato existente te obliga.

Records y tipos inmutables

Los Records son inmutables, por lo que no necesitan clonación en absoluto — comparte la misma referencia en todas partes. Si necesitas una copia modificada, escribe pequeños métodos with...:

record Point(int x, int y) {
  Point withX(int newX) { return new Point(newX, y); }
}

Este estilo — "construir una nueva instancia con un campo cambiado" — suele ser más claro que clonar seguido de mutación.

Un ejemplo completo

java— editable, runs on the server

Qué viene a continuación

La mayor parte de los problemas relacionados con la clonación desaparece si la clase es inmutable desde el principio — nada que copiar de forma defensiva, sin sorpresas por aliasing, segura para compartir entre hilos. El siguiente capítulo explica lo que se necesita para diseñar una clase de esa manera. Continúa con clases inmutables en Java.

Práctica

Práctica
¿Qué hace el `Object.clone()` predeterminado con un campo que contiene una lista mutable?
¿Qué hace el `Object.clone()` predeterminado con un campo que contiene una lista mutable?
Was this page helpful?