Método clone() do Objeto Java – Clonagem em Java

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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 em this.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

  1. 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.

  2. Você também pode definir um construtor de cópia se a sua classe tiver principalmente propriedades mutáveis.

  3. 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.

  4. 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.

  5. 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