Clonagem de Objetos Java
Copie objetos Java com clone(), a interface Cloneable e as diferenças entre cópias rasas e profundas.
Clonar um objeto significa produzir um novo objeto com o mesmo estado — uma cópia que pode ser mutada independentemente do original. A resposta nativa do Java é Object.clone() combinado com a interface marcadora Cloneable, mas o design tem arestas suficientes para que a maior parte do código moderno recorra a um construtor de cópia ou a um método fábrica no lugar. Este capítulo mostra as duas rotas e a armadilha que existe entre elas.
A rota nativa: Object.clone()
Object.clone() é protected e realiza uma cópia rasa campo a campo da instância. Para usá-lo você precisa:
- Fazer sua classe implementar
Cloneable— uma interface marcadora sem métodos. Sem ela,clone()lançaCloneNotSupportedException. - Sobrescrever
clone()para torná-lopublice (geralmente) restringir o tipo de retorno à classe concreta.
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() produz a cópia real. O try/catch é burocracia: a exceção verificada está declarada em Object.clone, mas como nossa classe implementa Cloneable, a exceção é inatingível.
Cópia rasa: o que ela realmente faz
Uma cópia rasa duplica os campos imediatos. Referências dentro do objeto são copiadas como referências, não como novos objetos — portanto o original e o clone compartilham o que essas referências apontam:
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 arrayPara primitivos e valores imutáveis (String, Integer, LocalDate), a cópia rasa está bem. Para sub-objetos mutáveis, quase sempre está errada — mutar o clone afeta o original.
Cópia profunda: a correção
Para obter uma cópia verdadeiramente independente, sobrescreva clone() para copiar recursivamente os campos mutáveis:
@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);
}
}Arrays implementam Cloneable nativamente e copiam com .clone(). Coleções não — para um campo List<String> você escreveria copy.items = new ArrayList<>(items); (veja ArrayList). Para um grafo de seus próprios tipos mutáveis, todos os tipos do grafo precisam participar.
Por que Cloneable tem má reputação
Algumas peculiaridades tornam o clone() desconfortável:
- É uma interface marcadora sem nenhum método
clone()— o próprioCloneablenão expõe nada; o contrato está emObject.clone(). - Bypassa construtores — os campos do novo objeto são preenchidos pela JVM, portanto quaisquer invariantes impostos no construtor não são revalidados.
- Subclasses herdam a obrigação: se
Parentsobrescreveclone(), toda subclasse precisa manter a lógica de cópia profunda em sincronia, ou herda silenciosamente uma versão rasa quebrada. - A exceção verificada, o cast, a chamada a
super.clone()— cada sobrescrita repete o mesmo ruído.
A alternativa moderna: construtor de cópia
Um construtor de cópia é simplesmente um construtor que recebe uma instância da mesma classe e copia seus 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);Ele passa pelo construtor normal, então os invariantes são verificados. É Java puro — sem interface marcadora, sem CloneNotSupportedException, sem cast. Subclasses apenas escrevem seu próprio construtor de cópia que chama super(other). A recomendação do Effective Java é preferir construtores de cópia (ou fábricas estáticas copyOf) ao clone.
Classes parecidas com coleções já seguem esse padrão: new ArrayList<>(other), new HashMap<>(other), Set.copyOf(other).
Qual abordagem devo usar?
| Situação | Abordagem recomendada |
|---|---|
| Classe nova e simples que você controla | Construtor de cópia ou fábrica estática copyOf |
| Classe com apenas campos primitivos ou imutáveis | Qualquer uma — até um clone() raso é seguro |
| Classe com campos mutáveis (listas, arrays, objetos aninhados) | Construtor de cópia com cópias profundas explícitas |
API existente que já exige Cloneable | Sobrescreva clone() e faça cópia profunda dos campos mutáveis |
| Tipo valor que você pode redesenhar | Torne-o imutável — assim nenhuma cópia é necessária |
Em resumo: prefira um construtor de cópia para código novo e recorra a clone() apenas quando um contrato existente obrigar.
Records e tipos imutáveis
Records são imutáveis, portanto não precisam de clonagem alguma — compartilhe a mesma referência em todo lugar. Se precisar de uma cópia modificada, escreva pequenos métodos with...:
record Point(int x, int y) {
Point withX(int newX) { return new Point(newX, y); }
}Esse estilo — "construir uma nova instância com um campo alterado" — costuma ser mais claro do que clonar seguido de mutação.
Um exemplo completo
O que vem a seguir
A maior parte dos problemas com clonagem desaparece se a classe for imutável desde o início — nada para copiar defensivamente, sem surpresas de aliasing, seguro para compartilhar entre threads. O próximo capítulo explica o que é preciso para projetar uma classe dessa forma. Continue em classes imutáveis em Java.