Metodo clone() dell’oggetto Java – Clonazione in Java

Clonare è il processo di creazione di una copia di un Oggetto. La classe Java Object è dotata del metodo nativo clone() che restituisce la copia dell’istanza esistente. Poiché Object è la classe di base in Java, tutti gli oggetti supportano di default il cloning.

Clonazione di Oggetti in Java

Se si desidera utilizzare il metodo clone() di Java Object, è necessario implementare l’interfaccia segnaposto java.lang.Cloneable. Altrimenti, verrà generata un’eccezione CloneNotSupportedException durante l’esecuzione. Inoltre, il metodo clone di Object è un metodo protetto, quindi sarà necessario sostituirlo. Vediamo la clonazione di Oggetti in Java con un programma di esempio.

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

}

Stiamo utilizzando il metodo clone() di Object, quindi abbiamo implementato l’interfaccia Cloneable interfaccia. Stiamo chiamando il metodo clone() della superclasse, cioè il metodo clone() di Object.

Utilizzo del Metodo clone() di Object

Creiamo un programma di test per utilizzare il metodo clone() dell’oggetto per creare una copia dell’istanza.

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

		// Verificare se gli attributi emp e clonedEmp sono uguali o diversi
		System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
		
		System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
		
		// Vediamo l'effetto dell'uso del clonaggio predefinito
		
		// Modificare le proprietà di emp
		emp.getProps().put("title", "CEO");
		emp.getProps().put("city", "New York");
		System.out.println("clonedEmp props:" + clonedEmp.getProps());

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

	}

}

Output:

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj

CloneNotSupportedException in Runtime

Se la nostra classe Employee non implementerà l’interfaccia Cloneable, il programma sopra genererà un’eccezione di runtime CloneNotSupportedException exception.

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)

Comprensione della clonazione di oggetti

Osserviamo l’output sopra e comprendiamo cosa sta accadendo con il metodo clone() dell’oggetto.

  1. emp e clonedEmp == test: false: Significa che emp e clonedEmp sono due oggetti diversi, non si riferiscono allo stesso oggetto. Questo è in accordo con il requisito di clonazione degli oggetti in Java.
  2. emp e clonedEmp HashMap == test: true: Quindi entrambe le variabili dell’oggetto emp e clonedEmp si riferiscono allo stesso oggetto. Questo può essere un grave problema di integrità dei dati se cambiamo il valore dell’oggetto sottostante. Qualsiasi modifica del valore potrebbe essere riflessa anche nell’istanza clonata.
  3. proprietà clonedEmp:{city=New York, salary=10000, title=CEO}: Non abbiamo apportato alcuna modifica alle proprietà di clonedEmp, ma sono comunque cambiate perché le variabili emp e clonedEmp si riferiscono allo stesso oggetto. Questo accade perché il metodo di clonazione predefinito di Object crea una copia superficiale. Può essere un problema quando si desidera creare oggetti totalmente separati attraverso il processo di clonazione. Ciò può portare a risultati indesiderati, quindi c’è bisogno di sovrascrivere correttamente il metodo clone() di Object.
  4. nome clonedEmp:Pankaj: Cosa è successo qui? Abbiamo cambiato il nome di emp ma il nome di clonedEmp non è cambiato. È perché String è immutabile. Quindi quando stiamo impostando il nome di emp, viene creato una nuova stringa e il riferimento del nome di emp viene cambiato in this.name = name;. Quindi il nome di clonedEmp rimane invariato. Troverai un comportamento simile anche per qualsiasi tipo di variabile primitiva. Quindi siamo a posto con la clonazione predefinita dell’oggetto Java finché abbiamo solo variabili primitive e immutabili nell’oggetto.

Tipi di Clonazione degli Oggetti

Ci sono due tipi di clonazione degli oggetti – clonazione superficiale e clonazione profonda. Capiamo ciascuno di essi e scopriamo il modo migliore per implementare la clonazione nei nostri programmi Java.

1. Clonazione superficiale

L’implementazione predefinita del metodo clone() di Java utilizza una copia superficiale. Utilizza l’API reflection per creare la copia dell’istanza. Il seguente frammento di codice mostra l’implementazione della clonazione superficiale.

@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. Clonazione profonda

Nella clonazione profonda, dobbiamo copiare i campi uno per uno. Se abbiamo un campo con oggetti nidificati come List, Map, ecc., allora dobbiamo scrivere il codice per copiarli anche uno per uno. Ecco perché viene chiamata clonazione profonda o copia profonda. Possiamo sovrascrivere il metodo clone di Employee come nel seguente codice per la clonazione profonda.

public Object clone() throws CloneNotSupportedException {

	Object obj = super.clone(); //utilize clone Object method

	Employee emp = (Employee) obj;

	// clonazione profonda per campi immutabili
	emp.setProps(null);
	Map hm = new HashMap<>();
	String key;
	Iterator it = this.props.keySet().iterator();
	// Copia profonda campo per campo
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, this.props.get(key));
	}
	emp.setProps(hm);
	
	return emp;
}

Con questa implementazione del metodo clone(), il nostro programma di test produrrà l’output seguente.

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj

Nella maggior parte dei casi, questo è ciò che vogliamo. Il metodo clone() dovrebbe restituire un nuovo oggetto completamente separato dall’istanza originale. Quindi, se stai pensando di utilizzare Object clone e la clonazione nel tuo programma, fallo saggiamente e sovrascrivi correttamente, facendo attenzione ai campi mutabili. Potrebbe essere un compito difficile se la tua classe estende altre classi che a loro volta estendono altre classi e così via. Dovrai risalire fino alla gerarchia di ereditarietà di Object per occuparti della copia profonda di tutti i campi mutabili.

Clonazione utilizzando la serializzazione?

Un modo per eseguire facilmente la clonazione profonda è tramite la serializzazione. Ma la serializzazione è una procedura costosa e la tua classe dovrebbe implementare l’interfaccia Serializable. Tutti i campi e le superclassi devono implementare Serializable anche loro.

Utilizzo di Apache Commons Util

Se stai già utilizzando le classi di Apache Commons Util nel tuo progetto e la tua classe è serializzabile, allora utilizza il seguente metodo.

Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);

Copia Costruttore per Clonare

Possiamo definire un costruttore di copia per creare una copia dell’oggetto. Perché dipendere dal metodo clone() dell’oggetto del tutto? Ad esempio, possiamo avere un costruttore di copia per l’Employee come nel seguente codice.

public Employee(Employee emp) {
	
	this.setId(emp.getId());
	this.setName(emp.getName());
	
	Map hm = new HashMap<>();
	String key;
	Iterator it = emp.getProps().keySet().iterator();
	// Copia profonda campo per campo
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, emp.getProps().get(key));
	}
	this.setProps(hm);

}

Ogni volta che abbiamo bisogno di una copia dell’oggetto dell’impiegato, possiamo ottenerla usando Employee clonedEmp = new Employee(emp);. Tuttavia, scrivere un costruttore di copia può essere un lavoro tedioso se la tua classe ha molti variabili, soprattutto primitive e immutabili.

Pratiche Migliori per il Clonaggio degli Oggetti Java

  1. Utilizza il metodo clone() predefinito dell’oggetto solo quando la tua classe ha variabili primitive e immutabili o desideri una copia superficiale. In caso di ereditarietà, dovrai controllare tutte le classi che stai estendendo fino al livello dell’oggetto.

  2. Puoi anche definire un costruttore di copia se la tua classe ha principalmente proprietà mutabili.

  3. Utilizza il metodo clone() dell’oggetto chiamando super.clone() nel metodo clone sovrascritto, quindi apporta le modifiche necessarie per la copia profonda dei campi mutabili.

  4. Se la tua classe è serializzabile, puoi utilizzare la serializzazione per la clonazione. Tuttavia, ci sarà un impatto sulle prestazioni, quindi effettua dei benchmark prima di utilizzare la serializzazione per la clonazione.

  5. Se stai estendendo una classe e ha definito correttamente il metodo di clonazione utilizzando la copia profonda, allora puoi utilizzare il metodo di clonazione predefinito. Ad esempio, abbiamo definito correttamente il metodo clone() nella classe Employee come segue.

    @Override
    public Object clone() throws CloneNotSupportedException {
    
    	Object obj = super.clone();
    
    	Employee emp = (Employee) obj;
    
    	// clonazione profonda per i campi immutabili
    	emp.setProps(null);
    	Map<String, String> hm = new HashMap<>();
    	String key;
    	Iterator<String> it = this.props.keySet().iterator();
    	// Copia profonda campo per campo
    	while (it.hasNext()) {
    		key = it.next();
    		hm.put(key, this.props.get(key));
    	}
    	emp.setProps(hm);
    
    	return emp;
    }
    

    Possiamo creare una classe figlia e utilizzare la clonazione profonda della superclasse come segue.

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

    La classe EmployeeWrap non ha proprietà mutevoli e sta utilizzando l’implementazione del metodo clone() della superclasse. Se ci sono campi mutabili, allora dovrai occuparti della copia profonda solo di quei campi. Ecco un programma semplice per testare se questo modo di clonare funziona bene o no.

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

    Output:

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

    Quindi ha funzionato perfettamente come ci aspettavamo.

Ecco tutto sulla clonazione degli oggetti in Java. Spero tu abbia avuto un’idea del metodo clone() degli oggetti Java e di come sovrascriverlo correttamente senza alcun effetto negativo.

Puoi scaricare il progetto dal mio Repository GitHub.

Riferimento: Documentazione API per il clone degli oggetti

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