avr.
2008
Java Quiz #10
En C++, pour dupliquer un objet, on utilise habituellement le constructeur par recopie. C'est un constructeur qui n'attend qu'un argument, de même type que le type de celui que l'on construit (voir par exemple le constructeur String(String)).
En Java, Sun recommande d'implémenter l'interface Cloneable et d'utiliser la méthode clone(), plutôt qu'un constructeur par recopie, pour dupliquer un objet.
Pourquoi ?
Réponse : Quand on considère une classe tout seule, les 2 syntaxes se valent. Le problème se pose lorsqu'on définit une hiérarchie de classes. Par exemple :
class Employe { ... } class Manager extends Employe { ... } class Prestataire extends Employe { ... } ... Collection lesEmployesDeMonBatiment = ...;
Ici la collection contient des objets, qui sont soit des employés, soit des managers, soit des prestataires (oui, on s'interdit les managers prestataires ! ;-) ). Comment dupliquer le contenu de la liste ?
Avec le constructeur par recopie, il faut écrire :
Collection collectionCopiee = ...; // Cree une nouvelle collection vide for (Employe employe : lesEmployesDeMonBatiment) { Employe employeCopie; if (employe instanceof Manager) { employeCopie = new Manager((Manager) employe); } else if (employe instanceof Prestataire) { employeCopie = new Prestataire((Prestataire) employe); } else employeCopie = new Employe(employe); } collectionCopiee.add(employeCopie); }
Et à chaque fois qu'un nouveau sous-type de Employe
est créé, il faut modifier ce bloc de code, et ajouter un autre "if (employe instanceof ...) {
" sur ce nouveau sous-type (quelle magnifique évolutivité du code !)
En Java, avec l'interface Cloneable
(que doit implémenter Employe
), il suffit d'écrire (et ce, quel que soit le nombre de sous-classes d'Employe
) :
Collection collectionCopiee = ...; // Cree une nouvelle collection vide for (Employe employe : lesEmployesDeMonBatiment) { collectionCopiee.add(employe.clone()); }
Plus simple, non ?
Question subsidiaire : pourquoi, en Java, faut-il explicitement implémenter l'interface Cloneable
pour qu'un objet puisse être cloné, alors que la méthode clone()
est définie dans la classe Object
(donc pour tout objet) ?
Réponse : parce que le clonage n'a pas toujours de sens pour un objet. Car autant cloner une collection a du sens, autant cloner l'unique instance de Runtime
(qui représente la machine virtuelle) n'en a pas. Forcer à implémenter l'interface Cloneable
permet donc de contrôler finement ce qui peut être cloné de ce qui ne doit pas l'être.
Commentaires
Basiquement en C++ ce que j'ai vu faire (en mission, en C++) c'est un memcpy : on copie bit à bit le contenu de la zone mémoire qui contient l'objet source, vers une zone mémoire qui contiendra l'objet cible (zone qu'on a alloué de la taille de l'objet avec un malloc). Bilan on a un pointeur sur le nouvel objet. La conséquence c'est en particulier que les membres pointeurs sont copiés à l'identique : l'objet cible pointe sur les même zones mémoire que l'objet source.
Exemple :
ObjDst.pMembre vaudra après "memcpy" la même chose que ObjSrc.pMembre, et l'accès ObjDst->pMembre est une violation du principe d'encapsulation.
Si on veut faire propre il faut donc non seulement faire un memcpy mais également ensuite des new/malloc/mise à null pour tous les pointeurs membres. Ce petit témoignage pour souligner un des nombreux écueils que représentent les pointeurs : avec eux il faut toujours faire très attention à ce qu'on code.
Du coup un élément de réponse : et si en fait on voulait vraiment avoir des références plutôt que de nouveaux membres (dans mon exemple : et si on voulait vraiment que pMembre soit le même dans les deux classes ?). Je pense que c'est pour ça que clone() est là en Java : do what you really want.