Dupliquer un objet ou un tableau ne peut pas se faire par recopie d'une référence. Observez le programme suivant :
class Copie {
public static void main(String arg[]) {
int[] tableau = {1, 2};
int[] table;
table = tableau;
table[0] = 7;
System.out.println("tableau contient : "+ tableau[0] + ", " + tableau[1]);
}
}
On obtient :
tableau contient : 7, 2
Les attributs table et tableau référencent le même objet, c'est pourquoi changer table[0] change aussi tableau[0] ; table n'est en rien une copie de tableau.
La méthode clone() de la classe Object est prévue pour dupliquer des objets. Elle a pour en-tête :
protected Object clone()
throws CloneNotSupportedException
La méthode clone de la classe Object duplique tous les attributs d'une classe mais pas le "contenu" des attributs de type référence. Si une classe A contient :
On appelle la copie faite pas la méthode clone de la classe Object une "copie de surface", ou "copie superficielle" (shallow copy en anglais), qui s'oppose à une "copie en profondeur"(deep copy en anglais) que nous illustrons plus bas. Dans une copie en profondeur, il faut faire récursivement des copies en profondeur des objets ou des tableaux contenus par une instance de l'objet à dupliquer.
Pour utiliser la méthode clone sur une instance d'une classe A, il est généralement nécessaire que la classe A redéfinisse la méthode clone de la classe Object. En effet, la méthode clone de la classe Object est protected ; cela implique qu'on ne peut l'invoquer que du paquetage java.lang (ce que vous ne ferez sûrement pas) ou en utilisant, dans une classe A, la méthode clone sur une instance de type A ou sous-type de A ; cette opération est rarement effectuée sauf dans une méthode clone redéfinie et en invoquant alors super.clone().
Si vous définissez une classe A et que vous voulez permettre le clonage des éléments de A :
Certaines classes de l'API implémentent l'interface Cloneable, comme par exemple la classe java.util.ArrayList, qui ne fait néanmoins qu'une copie de surface.
L'exemple ci-dessous illustre comment cloner une classe ; la classe ObjetClonable redéfinit la méthode clone en prévoyant une copie en profondeur, avec néanmoins une particularité (pour montrer ce qui se passe) ; le contenu de la référence entierB n'est pas cloné, contrairenement à ce qui est fait avec entierA. Vous pouvez constater en regardant l'exécution de la méthode main de la classe EssaiClone que l'instance de la classe ObjetClonable est clonée en profondeur avec la méthode clone définie dans cette classe, sauf en ce qui concerne l'objet référencé par entierB dont on n'a dupliqué que la référence.
Notre exemple nécessite plusieurs classes. On a mis les classes EntierA, EntierB et ObjetClonable dans le paquetage clonables.
Dans le fichier EntierA.java :
package clonables; public class EntierA implements Cloneable { private int entier = 10; public int getEntier() { return entier; } public void setEntier(int entier) { this.entier = entier; } @Override public EntierA clone() throws CloneNotSupportedException { return (EntierA)super.clone(); } }La classe EntierA donne ainsi la possibilité de cloner ses instances, et, dans son cas, le clonage en profondeur est identique au clonage superficiel.
Dans le fichier EntierB.java :
package clonables; public class EntierB { private int entier = 1000; public int getEntier() { return entier; } public void setEntier(int entier) { this.entier = entier; } }La classe EntierB n'est pas prévue pour que ses instances soient clonées.
Dans le fichier ObjetClonable.java :
package clonables; public class ObjetClonable implements Cloneable { private int entierLocal = 1; private EntierA entierA = new EntierA(); private int[] tableau = {0, 100}; private EntierB entierB = new EntierB(); @Override public ObjetClonable clone() throws CloneNotSupportedException { ObjetClonable copie = (ObjetClonable)super.clone(); copie.entierA = entierA.clone(); copie.tableau = new int[tableau.length]; System.arraycopy(tableau, 0, copie.tableau, 0 , tableau.length); return copie; } public int getEntierLocal() { return entierLocal; } public void setEntierLocal(int entier) { this.entierLocal = entier; } public int[] getTableau() { return tableau; } public EntierA getEntierA() { return entierA; } public EntierB getEntierB() { return entierB; } }Dans le fichier EssaiClone.java :
import clonables.ObjetClonable; class EssaiClone { public static void main(String arg[]) throws CloneNotSupportedException { ObjetClonable I = new ObjetClonable(), J; J = I.clone(); System.out.println("Dans l'original " + I.getEntierLocal() + " " + I.getEntierA().getEntier() + " " + I.getTableau()[1] + " " + I.getEntierB().getEntier()); System.out.println("Dans la copie " + J.getEntierLocal() + " " + J.getEntierA().getEntier() + " " + J.getTableau()[1] + " " + J.getEntierB().getEntier()); I.setEntierLocal(2); I.getEntierA().setEntier(20); I.getTableau()[1] = 200; I.getEntierB().setEntier(2000); System.out.println("\nApres changement de tout ce que contient l'original :"); System.out.println("Dans l'original " + I.getEntierLocal() + " " + I.getEntierA().getEntier() + " " + I.getTableau()[1] + " " + I.getEntierB().getEntier()); System.out.println("Dans la copie " + J.getEntierLocal() + " " + J.getEntierA().getEntier() + " " + J.getTableau()[1] + " " + J.getEntierB().getEntier()); } }On obtient à l'exécution :
Dans l'original 1 10 100 1000 Dans la copie 1 10 100 1000 Apres changement de tout ce que contient l'original : Dans l'original 2 20 200 2000 Dans la copie 1 10 100 2000
Pour accéder aux fichier :
EntierA.java
EntierB.java
ObjetClonable..java
EssaiClone.java
© Charon Irène Télécom-ParisTech 2010