Clonagem é o processo de criar uma cópia de um Objeto. A classe de Objeto Java vem com o método nativo \texttt{clone()} que retorna a cópia da instância existente. Como Objeto é a classe base em Java, todos os objetos por padrão suportam clonagem.
Clonagem de Objeto Java
Se você deseja usar o método \texttt{clone()} de Objeto Java, você precisa implementar a interface de marca \texttt{java.lang.Cloneable}. Caso contrário, ele lançará \texttt{CloneNotSupportedException} em tempo de execução. Além disso, a clonagem de Objeto é um método protegido, então você precisará sobrescrevê-lo. Vamos ver a clonagem de Objeto em Java com um programa de exemplo.
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class Employee implements Cloneable {
private int id;
private String name;
private Map<String, String> props;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getProps() {
return props;
}
public void setProps(Map<String, String> p) {
this.props = p;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Estamos usando o método \texttt{clone()} de Objeto, então implementamos a Cloneable
interface. Estamos chamando o método clone() da superclasse, ou seja, o método \texttt{clone()} de Objeto.
Usando o Método \texttt{clone()} de Objeto
Vamos criar um programa de teste para usar o método clone() do objeto para criar uma cópia da instância.
package com.journaldev.cloning;
import java.util.HashMap;
import java.util.Map;
public class CloningTest {
public static void main(String[] args) throws CloneNotSupportedException {
Employee emp = new Employee();
emp.setId(1);
emp.setName("Pankaj");
Map props = new HashMap<>();
props.put("salary", "10000");
props.put("city", "Bangalore");
emp.setProps(props);
Employee clonedEmp = (Employee) emp.clone();
// Verifique se os atributos emp e clonedEmp são iguais ou diferentes
System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
// Vamos ver o efeito de usar a clonagem padrão
// alterar propriedades de emp
emp.getProps().put("title", "CEO");
emp.getProps().put("city", "New York");
System.out.println("clonedEmp props:" + clonedEmp.getProps());
// alterar nome de emp
emp.setName("new");
System.out.println("clonedEmp name:" + clonedEmp.getName());
}
}
Resultado:
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj
CloneNotSupportedException em Tempo de Execução
Se nossa classe Employee não implementar a interface Cloneable
, o programa acima lançará a exceção CloneNotSupportedException
em tempo de execuçãoexception.
Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
at java.lang.Object.clone(Native Method)
at com.journaldev.cloning.Employee.clone(Employee.java:41)
at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)
Compreensão da Clonagem de Objetos
Vamos analisar a saída acima e entender o que está acontecendo com o método clone()
do objeto.
emp e clonedEmp == teste: false
: Isso significa que emp e clonedEmp são dois objetos diferentes, não se referindo ao mesmo objeto. Isso está de acordo com o requisito de clonagem de objetos em Java.emp e clonedEmp HashMap == teste: verdadeiro
: Então, tanto as variáveis de objeto emp quanto clonedEmp se referem ao mesmo objeto. Isso pode ser um problema sério de integridade de dados se alterarmos o valor do objeto subjacente. Qualquer alteração no valor pode ser refletida na instância clonada também.clonedEmp props:{cidade=Nova Iorque, salário=10000, título=CEO}
: Não fizemos nenhuma alteração nas propriedades de clonedEmp, mas ainda assim elas foram alteradas porque tanto as variáveis emp quanto clonedEmp estão se referindo ao mesmo objeto. Isso acontece porque o método clone() padrão de Object cria uma cópia rasa. Isso pode ser um problema quando você deseja criar objetos totalmente desvinculados por meio do processo de clonagem. Isso pode levar a resultados indesejados, portanto, a necessidade de substituir adequadamente o método clone() de Object.clonedEmp nome:Pankaj
: O que aconteceu aqui? Mudamos o nome emp, mas o nome de clonedEmp não mudou. É porque String é imutável. Então, quando estamos definindo o nome emp, uma nova string é criada e a referência do nome emp é alterada emthis.name = name;
. Portanto, o nome de clonedEmp permanece inalterado. Você encontrará um comportamento semelhante para qualquer tipo de variável primitiva também. Portanto, estamos bem com a clonagem padrão de objetos java desde que tenhamos apenas variáveis primitivas e imutáveis no objeto.
Tipos de Clonagem de Objetos
Existem dois tipos de clonagem de objetos – clonagem rasa e clonagem profunda. Vamos entender cada um deles e descobrir a melhor maneira de implementar a clonagem em nossos programas Java.
1. Clonagem Rasa
A implementação padrão do método clone() em Java usa a cópia rasa. Ele utiliza a API de reflexão para criar uma cópia da instância. O trecho de código abaixo mostra a implementação da clonagem rasa.
@Override
public Object clone() throws CloneNotSupportedException {
Employee e = new Employee();
e.setId(this.id);
e.setName(this.name);
e.setProps(this.props);
return e;
}
2. Clonagem Profunda
Na clonagem profunda, precisamos copiar os campos um por um. Se tivermos um campo com objetos aninhados, como List, Map, etc., então precisamos escrever o código para copiá-los também, um por um. É por isso que é chamado de clonagem profunda. Podemos substituir o método clone da classe Employee com o seguinte código para clonagem profunda.
public Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //utilize clone Object method
Employee emp = (Employee) obj;
// clonagem profunda para campos imutáveis
emp.setProps(null);
Map hm = new HashMap<>();
String key;
Iterator it = this.props.keySet().iterator();
// Cópia Profunda de campo por campo
while (it.hasNext()) {
key = it.next();
hm.put(key, this.props.get(key));
}
emp.setProps(hm);
return emp;
}
Com essa implementação do método clone(), nosso programa de teste produzirá a seguinte saída.
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj
Na maioria dos casos, é isso que queremos. O método clone() deve retornar um novo objeto totalmente desvinculado da instância original. Portanto, se você está pensando em usar Object clone e clonagem em seu programa, faça isso com sabedoria e substitua-o corretamente, cuidando dos campos mutáveis. Pode ser uma tarefa assustadora se sua classe estender outra classe que, por sua vez, estende outras classes e assim por diante. Você terá que percorrer toda a hierarquia de herança de Object para cuidar da cópia profunda de todos os campos mutáveis.
Clonagem usando Serialização?
Uma maneira de realizar clonagem profunda facilmente é através da serialização. No entanto, a serialização é um procedimento caro e sua classe deve implementar a interface Serializable
. Todos os campos e superclasses também devem implementar Serializable.
Usando Apache Commons Util
Se você já está usando as classes Apache Commons Util em seu projeto e sua classe é serializável, então use o método abaixo.
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
Construtor de Cópia para Clonagem
Podemos definir um construtor de cópia para criar uma cópia do objeto. Por que depender do método clone() do objeto? Por exemplo, podemos ter um construtor de cópia para a classe Employee como o código a seguir.
public Employee(Employee emp) {
this.setId(emp.getId());
this.setName(emp.getName());
Map hm = new HashMap<>();
String key;
Iterator it = emp.getProps().keySet().iterator();
// Cópia profunda campo por campo
while (it.hasNext()) {
key = it.next();
hm.put(key, emp.getProps().get(key));
}
this.setProps(hm);
}
Sempre que precisarmos de uma cópia do objeto Employee, podemos obtê-la usando Employee clonedEmp = new Employee(emp);
. No entanto, escrever um construtor de cópia pode ser uma tarefa tediosa se sua classe tiver muitas variáveis, especialmente primitivas e imutáveis.
Melhores Práticas para Clonagem de Objetos em Java
-
Use o método padrão clone() do Object apenas quando sua classe tiver variáveis primitivas e imutáveis ou se você deseja uma cópia rasa. Em caso de herança, será necessário verificar todas as classes que você está estendendo até o nível do Object.
-
Você também pode definir um construtor de cópia se a sua classe tiver principalmente propriedades mutáveis.
-
Utilize o método clone() do objeto chamando
super.clone()
no método clone sobrescrito, e faça as alterações necessárias para a cópia profunda dos campos mutáveis. -
Se sua classe for serializável, você pode usar a serialização para clonagem. No entanto, isso terá um impacto no desempenho, então faça alguns testes de desempenho antes de usar a serialização para clonagem.
-
Se você está estendendo uma classe e ela definiu corretamente o método clone usando cópia profunda, então você pode utilizar o método clone padrão. Por exemplo, nós definimos corretamente o método clone() na classe Employee da seguinte forma.
@Override public Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Employee emp = (Employee) obj; // clonagem profunda para campos imutáveis emp.setProps(null); Map<String, String> hm = new HashMap<>(); String key; Iterator<String> it = this.props.keySet().iterator(); // Cópia profunda campo por campo while (it.hasNext()) { key = it.next(); hm.put(key, this.props.get(key)); } emp.setProps(hm); return emp; }
Pode-se criar uma classe filha e utilizar a clonagem profunda da superclasse da seguinte forma.
package com.journaldev.cloning; public class EmployeeWrap extends Employee implements Cloneable { private String title; public String getTitle() { return title; } public void setTitle(String t) { this.title = t; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
A classe
EmployeeWrap
não possui propriedades mutáveis e está utilizando a implementação do método clone da superclasse. Se houver campos mutáveis, você terá que cuidar da cópia profunda apenas desses campos. Aqui está um programa simples para testar se essa forma de clonagem funciona bem ou não.package com.journaldev.cloning; import java.util.HashMap; import java.util.Map; public class CloningTest { public static void main(String[] args) throws CloneNotSupportedException { EmployeeWrap empWrap = new EmployeeWrap(); empWrap.setId(1); empWrap.setName("Pankaj"); empWrap.setTitle("CEO"); Map<String, String> props = new HashMap<>(); props.put("salary", "10000"); props.put("city", "Bangalore"); empWrap.setProps(props); EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone(); empWrap.getProps().put("1", "1"); System.out.println("Valor da propriedade mutável empWrap = "+empWrap.getProps()); System.out.println("Valor da propriedade mutável clonedEmpWrap = "+clonedEmpWrap.getProps()); } }
Saída:
Valor da propriedade mutável empWrap = {1=1, city=Bangalore, salary=10000} Valor da propriedade mutável clonedEmpWrap = {city=Bangalore, salary=10000}
Então funcionou perfeitamente como esperávamos.
Isso é tudo sobre a clonagem de objetos em Java. Espero que você tenha tido alguma ideia sobre o método clone() de objetos Java e como substituí-lo corretamente sem nenhum efeito adverso.
Você pode baixar o projeto do meu Repositório do GitHub.
Referência: Documentação da API para clone de objetos
Source:
https://www.digitalocean.com/community/tutorials/java-clone-object-cloning-java