Le clonage est le processus de création d’une copie d’un objet. La classe d’objet Java est livrée avec une méthode native clone()
qui renvoie la copie de l’instance existante. Étant donné que l’objet est la classe de base en Java, tous les objets prennent en charge le clonage par défaut.
Clonage d’objet en Java
Si vous souhaitez utiliser la méthode Java Object clone(), vous devez implémenter l’interface de balise
java.lang.Cloneable
. Sinon, cela générera une exception CloneNotSupportedException
à l’exécution. De plus, le clonage d’objet est une méthode protégée, donc vous devrez la réécrire. Examinons le clonage d’objet en Java avec un exemple de programme.
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();
}
}
Nous utilisons la méthode Object clone(), donc nous avons implémenté l’interface Cloneable
interface. Nous appelons la méthode clone() de la superclasse, c’est-à-dire la méthode Object clone().
Utilisation de la méthode Object clone()
Créons un programme de test pour utiliser la méthode clone() de l’objet afin de créer une copie de l’instance.
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();
// Vérifiez si les attributs emp et clonedEmp sont les mêmes ou différents
System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
// Voyons l'effet de l'utilisation du clonage par défaut
// changer les propriétés emp
emp.getProps().put("title", "CEO");
emp.getProps().put("city", "New York");
System.out.println("clonedEmp props:" + clonedEmp.getProps());
// changer le nom emp
emp.setName("new");
System.out.println("clonedEmp name:" + clonedEmp.getName());
}
}
Sortie :
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj
CloneNotSupportedException à l’exécution
Si notre classe Employee n’implémente pas l’interface Cloneable
, le programme ci-dessus lèvera une exception CloneNotSupportedException
à l’exécution .
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)
Compréhension du clonage d’objet
Examinons la sortie ci-dessus et comprenons ce qui se passe avec la méthode clone()
d’objet.
emp et clonedEmp == test: false
: Cela signifie que emp et clonedEmp sont deux objets différents, ne se référant pas au même objet. Cela est conforme à l’exigence de clonage d’objet Java.emp et clonedEmp HashMap == test: true
: Donc, les variables d’objet emp et clonedEmp font référence au même objet. Cela peut être un problème sérieux d’intégrité des données si nous changeons la valeur sous-jacente de l’objet. Tout changement de valeur pourrait être reflété dans l’instance clonée également.clonedEmp props:{city=New York, salary=10000, title=CEO}
: Nous n’avons apporté aucun changement aux propriétés de clonedEmp, mais elles ont quand même été modifiées car les variables emp et clonedEmp font référence au même objet. Cela se produit car la méthode clone() par défaut de l’objet crée une copie superficielle. Cela peut poser problème lorsque vous souhaitez créer des objets totalement détachés via le processus de clonage. Cela peut entraîner des résultats indésirables, d’où la nécessité de remplacer correctement la méthode clone() de l’objet.clonedEmp name:Pankaj
: Que s’est-il passé ici? Nous avons changé le nom de emp mais le nom de clonedEmp n’a pas changé. C’est parce que la chaîne de caractères est immuable. Donc, lorsque nous définissons le nom de emp, une nouvelle chaîne de caractères est créée et la référence du nom de emp est modifiée dansthis.name = name;
. Par conséquent, le nom de clonedEmp reste inchangé. Vous trouverez un comportement similaire pour tout type de variable primitive également. Donc, nous sommes bons avec le clonage par défaut des objets Java tant que nous n’avons que des variables primitives et immuables dans l’objet.
Types de clonage d’objets
Il existe deux types de clonage d’objets – clonage superficiel et clonage profond. Commençons par les comprendre et découvrons la meilleure façon de mettre en œuvre le clonage dans nos programmes Java.
1. Clonage superficiel
L’implémentation par défaut de la méthode clone() de l’objet Java utilise une copie superficielle. Elle utilise une API de réflexion pour créer une copie de l’instance. L’exemple de code suivant illustre l’implémentation du clonage superficiel.
@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. Clonage profond
Avec le clonage profond, nous devons copier les champs un par un. Si nous avons un champ avec des objets imbriqués tels que List, Map, etc., nous devons également écrire le code pour les copier un par un. C’est pourquoi on l’appelle clonage profond ou copie profonde. Nous pouvons substituer la méthode clone de la classe Employee avec le code suivant pour le clonage profond.
public Object clone() throws CloneNotSupportedException {
Object obj = super.clone(); //utilize clone Object method
Employee emp = (Employee) obj;
// clonage profond pour les champs immuables
emp.setProps(null);
Map hm = new HashMap<>();
String key;
Iterator it = this.props.keySet().iterator();
// Copie profonde champ par champ
while (it.hasNext()) {
key = it.next();
hm.put(key, this.props.get(key));
}
emp.setProps(hm);
return emp;
}
Avec cette implémentation de la méthode clone(), notre programme de test produira la sortie suivante.
emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj
Dans la plupart des cas, c’est ce que nous voulons. La méthode clone() devrait renvoyer un nouvel objet totalement détaché de l’instance originale. Donc, si vous envisagez d’utiliser Object clone et le clonage dans votre programme, faites-le judicieusement et remplacez-le correctement en prenant soin des champs mutables. Cela pourrait être une tâche redoutable si votre classe étend une autre classe qui étend à son tour une autre classe, et ainsi de suite. Vous devrez remonter toute la hiérarchie d’héritage de l’objet pour prendre soin de la copie en profondeur de tous les champs mutables.
Clonage en utilisant la sérialisation ?
Une manière de réaliser facilement le clonage en profondeur est par le biais de la sérialisation. Mais la sérialisation est une procédure coûteuse et votre classe devrait implémenter l’interface Serializable
. Tous les champs et superclasses doivent également implémenter Serializable.
Utilisation d’Apache Commons Util
Si vous utilisez déjà les classes Apache Commons Util dans votre projet et que votre classe est sérialisable, utilisez la méthode ci-dessous.
Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);
Constructeur de copie pour clonage
Nous pouvons définir un constructeur de copie pour créer une copie de l’objet. Pourquoi dépendre de la méthode clone() de l’objet ? Par exemple, nous pouvons avoir un constructeur de copie pour Employé comme le code suivant.
public Employee(Employee emp) {
this.setId(emp.getId());
this.setName(emp.getName());
Map hm = new HashMap<>();
String key;
Iterator it = emp.getProps().keySet().iterator();
// Copie profonde champ par champ
while (it.hasNext()) {
key = it.next();
hm.put(key, emp.getProps().get(key));
}
this.setProps(hm);
}
Chaque fois que nous avons besoin d’une copie de l’objet employé, nous pouvons l’obtenir en utilisant Employé employéClone = new Employé(employé);
. Cependant, écrire un constructeur de copie peut être un travail fastidieux si votre classe a beaucoup de variables, surtout primitives et immuables.
Meilleures pratiques de clonage d’objets Java
-
Utilisez la méthode clone() par défaut de l’objet uniquement lorsque votre classe a des variables primitives et immuables ou si vous voulez une copie superficielle. En cas d’héritage, vous devrez vérifier toutes les classes que vous étendez jusqu’au niveau de l’objet.
-
Vous pouvez également définir un constructeur de copie si votre classe a principalement des propriétés mutables.
-
Utilisez la méthode clone() de l’objet en appelant
super.clone()
dans la méthode clone remplacée, puis apportez les modifications nécessaires pour la copie profonde des champs mutables. -
Si votre classe est sérialisable, vous pouvez utiliser la sérialisation pour la clonage. Cependant, cela entraînera une perte de performances, alors faites quelques tests de performance avant d’utiliser la sérialisation pour le clonage.
-
Si vous étendez une classe et qu’elle a correctement défini la méthode clone en utilisant une copie profonde, alors vous pouvez utiliser la méthode clone par défaut. Par exemple, nous avons correctement défini la méthode clone() dans la classe Employee comme suit.
@Override public Object clone() throws CloneNotSupportedException { Object obj = super.clone(); Employee emp = (Employee) obj; // clonage profond pour les champs immuables emp.setProps(null); Map<String, String> hm = new HashMap<>(); String key; Iterator<String> it = this.props.keySet().iterator(); // Copie profonde champ par champ while (it.hasNext()) { key = it.next(); hm.put(key, this.props.get(key)); } emp.setProps(hm); return emp; }
Nous pouvons créer une classe enfant et utiliser le clonage profond de la superclasse comme suit.
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
n’a pas de propriétés mutables et elle utilise l’implémentation de la méthode clone() de la superclasse. S’il y a des champs mutables, alors vous devrez prendre soin de la copie profonde de ces champs uniquement. Voici un programme simple pour tester si cette méthode de clonage fonctionne correctement ou non.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()); } }
Sortie :
empWrap mutable property value = {1=1, city=Bangalore, salary=10000} clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}
Donc, cela a parfaitement fonctionné comme prévu.
C’est tout ce qu’il y a à savoir sur la duplication d’objets en Java. J’espère que vous avez eu une idée de la méthode clone() des objets Java et comment la remplacer correctement sans aucun effet indésirable.
Vous pouvez télécharger le projet depuis mon Dépôt GitHub.
Référence : Documentation de l’API pour la méthode clone d’Object
Source:
https://www.digitalocean.com/community/tutorials/java-clone-object-cloning-java