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

Clonagem é o processo de criar uma cópia de um Objeto. A classe Java Object vem com o método nativo clone() que retorna a cópia da instância existente. Como Object é a classe base em Java, todos os objetos por padrão suportam clonagem.

Clonagem de Objetos em Java

Se você deseja usar o método clone() de Object em Java, você precisa implementar a interface marcadora java.lang.Cloneable. Caso contrário, ele lançará CloneNotSupportedException em tempo de execução. Além disso, o clone de Object é um método protegido, então você precisará sobrescrevê-lo. Vamos olhar a clonagem de objetos 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 clone() de Object, então implementamos a Cloneable interface. Estamos chamando o método clone() da superclasse, ou seja, o método clone() de Object.

Usando o Método clone() de Object

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();

		// Verificar 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
		
		// mudar propriedades emp
		emp.getProps().put("title", "CEO");
		emp.getProps().put("city", "New York");
		System.out.println("clonedEmp props:" + clonedEmp.getProps());

		// mudar nome emp
		emp.setName("new");
		System.out.println("clonedEmp name:" + clonedEmp.getName());

	}

}

Saída:

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á uma exceção de tempo de execução CloneNotSupportedException.

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)

Compreendendo a 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: Significa que emp e clonedEmp são dois objetos diferentes, não referindo ao mesmo objeto. Isso está de acordo com o requisito de clonagem de objetos em Java.
  2. emp e clonedEmp HashMap == teste: true: Portanto, as variáveis de objeto emp e clonedEmp referem-se ao mesmo objeto. Isso pode ser um problema sério de integridade de dados se alterarmos o valor subjacente do objeto. 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=Diretor Executivo}: Não fizemos nenhuma alteração nas propriedades do clonedEmp, mas ainda assim, elas foram alteradas porque as variáveis emp e clonedEmp estão se referindo ao mesmo objeto. Isso ocorre porque o método padrão clone() de Object cria uma cópia rasa. Isso pode ser um problema quando você deseja criar objetos totalmente separados por meio do processo de clonagem. Isso pode levar a resultados indesejados, daí a necessidade de substituir adequadamente o método clone() de Object.
  4. clonedEmp nome:Pankaj: O que aconteceu aqui? Alteramos o nome de emp, mas o nome de clonedEmp não mudou. Isso ocorre porque String é imutável. Portanto, quando estamos definindo o nome de emp, uma nova string é criada e a referência do nome de emp é alterada em this.name = name;. Portanto, o nome de clonedEmp permanece inalterado. Você encontrará comportamento semelhante para quaisquer tipos de variáveis primitivas 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 superficial e clonagem profunda. Vamos entender cada uma delas e descobrir a melhor maneira de implementar a clonagem em nossos programas Java.

1. Clonagem Superficial

A implementação padrão do método clone() do objeto Java usa a cópia superficial. Ele usa a API de reflexão para criar a cópia da instância. O código abaixo demonstra a implementação da clonagem superficial.

@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, temos que copiar os campos um a um. Se tivermos um campo com objetos aninhados, como List, Map, etc., então temos que escrever o código para copiá-los também um a um. É por isso que é chamado de clonagem profunda ou cópia profunda. Podemos substituir o método clone do Employee como 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 é o que queremos. O método clone() deve retornar um novo objeto totalmente desvinculado da instância original. Portanto, se você estiver pensando em usar Object clone e clonagem em seu programa, faça isso com sabedoria e substitua corretamente, cuidando dos campos mutáveis. Isso pode ser uma tarefa assustadora se sua classe estender outra classe que, por sua vez, estende outra classe e assim por diante. Você terá que percorrer toda a hierarquia de herança do Object para cuidar da cópia profunda de todos os campos mutáveis.

Clonagem usando Serialização?

Uma maneira fácil de realizar clonagem profunda é 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 Object? Por exemplo, podemos ter um construtor de cópia para Employee como no 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 a 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.

Práticas Recomendadas 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 sua classe tiver principalmente propriedades mutáveis.

  3. Utilize o método clone() do objeto chamando super.clone() no método clone sobrescrito, em seguida, 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ê estiver estendendo uma classe e ela tiver definido o método clone corretamente usando cópia profunda, então você pode utilizar o método clone padrão. Por exemplo, temos o método clone() adequadamente definido 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;
    }
    

    Podemos criar uma classe filha e utilizar a clonagem profunda da superclasse da seguinte maneira.

    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, será necessário 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("empWrap mutable property value = "+empWrap.getProps());
    
    		System.out.println("clonedEmpWrap mutable property value = "+clonedEmpWrap.getProps());
    		
    	}
    
    }
    

    Saída:

    empWrap mutable property value = {1=1, city=Bangalore, salary=10000}
    clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}
    

    Então funcionou perfeitamente como esperávamos.

Isso é tudo sobre clonagem de objetos em Java. Espero que você tenha 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 GitHub.

Referência: API Doc para clone de objetos.

Source:
https://www.digitalocean.com/community/tutorials/java-clone-object-cloning-java