web-dev-qa-db-fra.com

Copie profonde, copie superficielle, clone

J'ai besoin d'éclaircissements sur les différences entre copie profonde, copie superficielle et clone en Java.

73
trs

Malheureusement, "copie superficielle", "copie profonde" et "clone" sont des termes assez mal définis.


Dans le contexte Java, nous devons d’abord faire la distinction entre "copier une valeur" et "copier un objet".

int a = 1;
int b = a;     // copying a value
int[] s = new int[]{42};
int[] t = s;   // copying a value (the object reference for the array above)

StringBuffer sb = new StringBuffer("Hi mom");
               // copying an object.
StringBuffer sb2 = new StringBuffer(sb);

En bref, une affectation d'une référence à une variable dont le type est un type de référence est "copier une valeur" où la valeur est la référence de l'objet. Pour copier un objet, quelque chose doit utiliser new, explicitement ou sous le capot.


Passons maintenant à la copie "superficielle" par rapport à la copie "profonde" d'objets. La copie superficielle signifie généralement que vous ne copiez qu'un seul niveau d'un objet, tandis que la copie en profondeur signifie généralement la copie de plusieurs niveaux. Le problème est de décider ce que nous entendons par niveau. Considère ceci:

public class Example {
    public int foo;
    public int[] bar;
    public Example() { };
    public Example(int foo, int[] bar) { this.foo = foo; this.bar = bar; };
}

Example eg1 = new Example(1, new int[]{1, 2});
Example eg2 = ... 

L'interprétation normale est qu'une copie "superficielle" de eg1 Serait un nouvel objet Example dont foo est égal à 1 et dont le champ bar fait référence au même tableau comme dans l'original; par exemple.

Example eg2 = new Example(eg1.foo, eg1.bar);

L’interprétation normale d’une copie "profonde" de eg1 Serait un nouvel objet Example dont foo est égal à 1 et dont le champ bar fait référence à un copie de le tableau d'origine; par exemple.

Example eg2 = new Example(eg1.foo, Arrays.copy(eg1.bar));

(Les personnes provenant d’un arrière-plan C/C++ peuvent dire qu’une affectation de référence produit une copie superficielle. Cependant, ce n’est pas ce que nous entendons par copie superficielle dans le Java contexte ...)

Deux autres questions/zones d'incertitude existent:

  • Quelle est la profondeur? Est-ce que ça s'arrête à deux niveaux? Trois niveaux? Est-ce que cela signifie tout le graphe des objets connectés?

  • Qu'en est-il des types de données encapsulés? par exemple. un string? Une chaîne n'est en réalité pas un seul objet. En fait, il s'agit d'un "objet" avec des champs scalaires et une référence à un tableau de caractères. Cependant, le tableau de caractères est complètement masqué par l'API. Ainsi, lorsque nous parlons de copier une chaîne, est-il judicieux de l'appeler une copie "superficielle" ou une copie "profonde"? Ou devrions-nous simplement l'appeler une copie?


Enfin, clonez. Le clonage est une méthode qui existe sur toutes les classes (et tableaux) et qui est généralement considérée comme produisant une copie de l'objet cible. Pourtant:

  • La spécification de cette méthode ne spécifie pas délibérément s'il s'agit d'une copie superficielle ou profonde (en supposant qu'il s'agisse d'une distinction significative).

  • En fait, la spécification n'indique même pas spécifiquement que le clone produit un nouvel objet.

Voici ce que le javadoc dit:

"Crée et retourne une copie de cet objet. La signification précise de" copie "peut dépendre de la classe de l'objet. L'intention générale est que, pour tout objet x, l'expression x.clone() != x sera vrai et que l'expression x.clone().getClass() == x.getClass() sera vraie, mais il ne s'agit pas d'exigences absolues. S'il est généralement vrai que x.clone().equals(x) sera vrai, ceci n'est pas une exigence absolue. "

Notez que cela signifie qu’à une extrémité, le clone pourrait être l’objet cible et à l’autre extrémité, le clone pourrait ne pas être identique à l'original. Et cela suppose que le clone est même pris en charge.

En bref, cloner signifie potentiellement quelque chose de différent pour chaque classe Java.


Certaines personnes prétendent (comme @supercat le fait dans les commentaires) que la méthode Java clone()] _ est brisée. Mais je pense que la conclusion correcte est que le concept de clone est rompu dans le contexte de OO. D'après les informations dont je dispose, il est impossible de développer un modèle unifié de clonage cohérent et utilisable pour tous les types d'objets.

110
Stephen C

Le terme "clone" est ambigu (bien que la bibliothèque de classes Java inclut une interface Cloneable )) et puisse désigner une copie profonde ou une copie superficielle. Copies profondes/superficielles ne sont pas spécifiquement liés à Java mais sont un concept général relatif à la copie d'un objet et font référence à la manière dont les membres d'un objet sont également copiés.

Par exemple, supposons que vous ayez une classe de personnes:

class Person {
    String name;
    List<String> emailAddresses
}

Comment cloner des objets de cette classe? Si vous effectuez une copie superficielle, vous pouvez copier le nom et mettre une référence à emailAddresses dans le nouvel objet. Mais si vous modifiiez le contenu de la liste emailAddresses, vous modifieriez la liste dans les deux copies (puisque c'est ainsi que fonctionnent les références d'objet).

Une copie complète signifierait que vous copiez chaque membre de manière récursive. Vous devez donc créer un nouveau List pour le nouveau Person, puis copier le contenu de l'ancien vers le nouvel objet.

Bien que l'exemple ci-dessus soit trivial, les différences entre les copies profonde et superficielle sont importantes et ont un impact majeur sur toute application, en particulier si vous essayez de concevoir une méthode de clonage générique à l'avance, sans savoir comment elle pourrait être utilisée ultérieurement. Il y a des moments où vous avez besoin d'une sémantique profonde ou peu profonde, ou d'un hybride où vous copiez en profondeur certains membres mais pas d'autres.

22
Adam Batkin
  • Copie profonde: Cloner cet objet et chaque référence à chaque autre objet
  • Copie peu profonde: cloner cet objet et conserver ses références
  • L'objet clone () lève CloneNotSupportedException: il n'est pas spécifié si cela doit renvoyer une copie profonde ou peu profonde, mais au minimum: o.clone ()! = O
18
Sam Pullara

Les termes "copie superficielle" et "copie profonde" sont un peu vagues; Je suggérerais d'utiliser les termes "membre par membre" et ce que j'appellerais un "clone sémantique". Un "clone membre par membre" d'un objet est un nouvel objet, du même type d'exécution que l'original, pour chaque champ, le système exécute effectivement "newObject.field = oldObject.field". La base Object.Clone () effectue un clone membre par membre; Le clonage membre est généralement le droit point de départ pour cloner un objet, mais dans la plupart des cas, un "travail de réparation" sera nécessaire après un clone membre par membre. Dans de nombreux cas, toute tentative d'utilisation d'un objet produit via le clone memberwise sans effectuer au préalable les corrections nécessaires entraînera des problèmes tels que la corruption de l'objet cloné et éventuellement d'autres objets. Certaines personnes utilisent le terme "clonage superficiel" pour désigner le clonage par membre, mais ce n'est pas la seule utilisation du terme.

Un "clone sémantique" est un objet qui contient les mêmes données que l'original, du point de vue du type. Pour examiner, considérons une BigList qui contient un tableau> et un compte. Un clone de niveau sémantique d'un tel objet effectue un clone membre par membre, puis remplace le tableau> par un nouveau tableau, crée de nouveaux tableaux imbriqués et copie tous les T des tableaux d'origine dans les nouveaux. Il ne tenterait aucune sorte de clonage en profondeur des T.. Ironiquement, certaines personnes parlent de clonage "superficiel", d'autres l'appellent "clonage profond". Terminologie pas vraiment utile.

Bien qu'il existe des cas où le clonage réellement profond (copier récursivement tous les types mutables) est utile, il ne devrait être effectué que par des types dont les constituants sont conçus pour une telle architecture. Dans de nombreux cas, le véritable clonage en profondeur est excessif et peut interférer avec les situations où il est nécessaire d’obtenir un objet dont le contenu visible fait référence aux mêmes objets qu’un autre (c’est-à-dire une copie de niveau sémantique). Dans les cas où le contenu visible d'un objet est dérivé de manière récursive par rapport à d'autres objets, un clone de niveau sémantique impliquerait un clone profond récursif, mais dans les cas où le contenu visible n'est qu'un type générique, le code ne devrait pas tout cloner en profondeur. cela ressemble à une possibilité de clonage profond.

2
supercat