next up previous contents index
suivant: 5.7 Interprétation des expressions monter: 5. Tout un monde précédent: 5.5 Exemple : arbres,   Table des matières   Index


5.6 Clonage d'objets

Rappelons que l'affectation d'un objet à une variable écrit dans cette variable une référence à l'objet, mais ne crée pas un nouvel objet. Si l'on veut dupliquer un objet, on peut recourir à la méthode clone, définie dans la classe Object. Celle-ci commence par tester si la classe de l'objet réalise l'interface Cloneable (voir § [*]) ; si c'est le cas, elle effectue une copie superficielle de l'objet, et sinon, elle déclenche l'exception CloneNotSupportedException. Le transtypage (A) est nécessaire, car clone() retourne un Object, pas un A :

  A a1 = new A();
  A a2 = (A)a1.clone();

Cependant, comme la visibilité de clone est « protected », cette invocation ne peut figurer qu'à l'intérieur de la classe de l'objet à dupliquer ; sinon, le message d'erreur « clone() has protected access in java.lang.Object » est émis par le compilateur. Afin de pouvoir dupliquer un objet utilisé dans une classe quelconque, il faut redéfinir la méthode clone dans sa classe, en invoquant super.clone() et en la rendant publique :

class A implements Cloneable {
  // ...
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}

Si l'on veut pouvoir invoquer clone sans être obligé de traiter CloneNotSupportedException, il suffit de récupérer cette exception dans la redéfinition de clone, par exemple de la façon suivante :

  public Object clone() {
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      throw new InternalError(e.toString());
  }

5.6.0.0.1 Clonage des tableaux

Les types de tableaux héritent de toutes les méthodes publiques de la classe Object, tandis que la méthode clone est redéfinie comme une méthode publique (donc utilisable) qui ne déclenche pas l'exception CloneNotSupportedException ; elle réalise une copie d'un tableau :

   int[] a = new int[] {1, 2, 3, 4};
   int[] b = (int[]) a.clone();

Figure: Clonage d'un tableau de références : les objets sont partagés.
\begin{figure}\begin{center}
\leavevmode
\fbox{
\epsfig{file=fig/tabclone.eps}
} \end{center} \end{figure}

Signalons cette méthode clone réalise une copie superficielle d'un tableau (le tableau est copié, mais pas les objets auxquels référent ses éléments, figure [*]) :

  Point[] tp1 = (Point[]) tp.clone();


5.6.0.0.2 Clonage profond

La méthode clone définie dans Object ou redéfinie pour les tableaux est utile quand les objets d'une structure de données sont exactement représentés par l'ensemble des valeurs de leurs champs : ceci est le cas si tous les champs sont d'un type primitif (par exemple, la classe des nombres complexes, qui a deux champs de type double), ou pour un tableau d'éléments de type primitif. Dans d'autres cas, par exemple pour des arbres binaires, la copie d'une structure ArbreBinaire n'entraine pas une copie de l'arbre complet, mais seulement de la racine :

    ArbreBinaire a = 
      new ACons(0, 
                new ACons(1, AVide.val(), AVide.val()),
                new ACons(2, AVide.val(), AVide.val()));
    ArbreBinaire a1 = a;
    ArbreBinaire a2 = (ArbreBinaire)a.clone();

où le type ArbreBinaire est une interface, dont les deux classes d'implémentation AVide et ACons représentent respectivement l'arbre vide et les arbres non vides.

Figure: Co-référencement par copie superficielle.
\begin{figure}\begin{center}
\leavevmode
\fbox{
\epsfig{file=fig/cell_share.eps}
} \end{center} \end{figure}

Alors que a1 et a désignent le même objet, la définition de a2 crée un nouvel objet, mais les objets au deuxième niveau de l'arbre sont identiques : il y a partage d'objets par co-référencement (figure [*]). Une modification d'un n\oeud de a2 revient donc à une modification de a et de a1, ce qui n'est pas toujours souhaité.

Il faut donc redéfinir la méthode clone pour la classe ArbreBinaire afin qu'elle parcoure l'arbre et effectue des copies de tous les objets, et pas seulement de celui qui figure à sa racine (figure [*]). Un parcours en profondeur donne la fonction suivante :

interface ArbreBinaire {
  // ...
  Object clone();
}

class AVide implements ArbreBinaire {
  // ...
  public Object clone() {
    return this;
  }
}

class ACons implements ArbreBinaire {
  // ...
  public Object clone() {
    try {
      ACons a = (ACons) super.clone();                                  
      if (gauche != null) a.gauche = (ArbreBinaire)gauche.clone();
      if (droit != null) a.droit = (ArbreBinaire)droit.clone(); 
      return a;
    }
    catch (CloneNotSupportedException e) {
      throw new InternalError(e.toString());
    }   
  }
}

En général, on souhaite que pour un objet $x$, la valeur de x == x.clone() soit false. Le cas de la classe AVide est une exception, puisqu'elle est définie afin de n'admettre qu'une seule instance, l'arbre vide : un clone de l'arbre vide sera donc identique à l'arbre vide.

Figure: Clonage d'un arbre.
\begin{figure}\begin{center}
\leavevmode
\fbox{
\epsfig{file=fig/cell_clone.eps}
} \end{center} \end{figure}


next up previous contents index
suivant: 5.7 Interprétation des expressions monter: 5. Tout un monde précédent: 5.5 Exemple : arbres,   Table des matières   Index
Rene' LALEMENT 2001-11-07