Dupliquer un objet                        

Dupliquer un objet

     

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 :

la méthode clone de la classe Objectappliquée à une instance a de A construit une nouvelle instance de A avec : Par ailleurs, la méthode clone de la classe Object est programmée pour vérifier que l'objet concerné implémente l'interface java.lang.cloneable, ce qui doit être indiqué dans l'en-tête de la classe par : implements Cloneable. Si la méthode clone de la classe Object est invoquée sur objet n'implémentant pas l'interface Cloneable, elle envoie une exception du type CloneNotSupportedException, ce qui conduit au minimum à l'échec du clonage.
L'interface Cloneable ne contient ni constante ni prototype de méthode, elle ne sert que pour savoir si il est prévu ou non qu'un objet soit cloné.

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


return (entierA)super.clone(); : on appelle la méthode clone de la classe Object qui fonctionnera ici car :
@Override : On utilise ce qu'on appelle une annotation qui s'adresse ici au compilateur. Celui-ci vérifie à la compilation que la méthode qui suit est bien la redéfinition d'une méthode définie dans une superclasse.
ObjetClonable copie = (ObjetClonable)super.clone(); : la conversion de type est ici nécessaire car la méthode clone de la classe Object annonce retourné un Object.
entierA.clone(); : on utilise la méthode clone redéfinie dans la classe EntierA. Si la classe EntierA ne redéfinissait pas cette méthode, la compilation n'aboutirait pas: il serait signalé qu'il est impossible d'accéder à la méthode protégée clone de la classe Object.
System.arraycopy(tableau, 0, copie.tableau, 0 , tableau.length); : la méthode statique arraycopy de la classe java.lang.System copie ici tableau, à partir de l'indice 0, dans copie.tableau, à partir de l'indice 0, et tableau.length éléments sont copiés. Il s'agit encore d'une copie de surface, c'est une copie des références ; si le tableau contenait des objets, il faudrait cloner ceux-ci si on voulait avoir un tableau de clones des objets.
Irene Charon

© Charon Irène Télécom-ParisTech 2010