À propos de Java clonable

95

Je cherchais des tutoriels expliquant Java Cloneable, mais je n'ai pas obtenu de bons liens, et Stack Overflow devient de toute façon un choix plus évident.

J'aimerais savoir ce qui suit:

  1. Cloneablesignifie que nous pouvons avoir un clone ou une copie d'objets, en implémentant l' Cloneableinterface. Quels sont les avantages et les inconvénients de faire cela?
  2. Comment se produit le clonage récursif si l'objet est un objet composite?
rêveur
la source
2
Les avantages et les inconvénients par rapport à quoi?
Galactus
4
J'ai lu que cela signifiait l'avantage d'une classe clonable par rapport à non. Je ne sais pas comment cela pourrait être interprété
autrement

Réponses:

159

La première chose que vous devez savoir Cloneableest de ne pas l'utiliser.

Il est très difficile de mettre en œuvre le clonage avec le Cloneabledroit, et l'effort n'en vaut pas la peine.

Au lieu de cela, utilisez d'autres options, comme apache-commons SerializationUtils(deep-clone) ou BeanUtils(shallow-clone), ou utilisez simplement un constructeur de copie.

Voir ici le point de vue de Josh Bloch sur le clonage avec Cloneable, ce qui explique les nombreux inconvénients de l'approche. ( Joshua Bloch était un employé de Sun et a dirigé le développement de nombreuses fonctionnalités Java.)

Bozho
la source
1
J'ai lié les mots de Bloch (au lieu de les citer)
Bozho
3
Notez que Block dit de ne pas utiliser Cloneable. Il ne dit pas de ne pas utiliser le clonage (ou du moins j'espère que non). Il existe de nombreuses façons d'implémenter simplement le clonage qui sont bien plus efficaces que des classes comme SerializationUtils ou BeanUtils qui utilisent la réflexion. Voir mon message ci-dessous pour un exemple.
Charles
Quelle est l'alternative à un constructeur de copie lors de la définition des interfaces? ajouter simplement une méthode de copie?
benez
@benez je dirais oui. depuis java-8, vous pouvez avoir des staticméthodes dans les interfaces, alors fournissez simplement un static WhatEverTheInterface copy(WhatEverTheInterface initial)? mais je me demande ce que cela vous donne, puisque vous copiez les champs d'un objet lors du clonage, mais une interface ne définit que des méthodes. soin d'expliquer?
Eugene
40

Cloneable lui-même n'est malheureusement qu'une interface de marqueur, c'est-à-dire qu'il ne définit pas la méthode clone ().

Cela change le comportement de la méthode Object.clone () protégée, qui lèvera une CloneNotSupportedException pour les classes qui n'implémentent pas Cloneable, et effectuera une copie superficielle par membre pour les classes qui le font.

Même si c'est le comportement que vous recherchez, vous devrez toujours implémenter votre propre méthode clone () pour la rendre publique.

Lors de l'implémentation de votre propre clone (), l'idée est de commencer par l'objet créé par super.clone (), qui est garanti d'être de la classe correcte, puis de faire toute population supplémentaire de champs au cas où une copie superficielle ne serait pas ce que tu veux. L'appel d'un constructeur depuis clone () serait problématique car cela briserait l'héritage au cas où une sous-classe voudrait ajouter sa propre logique clonable supplémentaire; s'il appelait super.clone (), il obtiendrait un objet de la mauvaise classe dans ce cas.

Cette approche contourne toute logique qui peut être définie dans vos constructeurs, ce qui pourrait potentiellement être problématique.

Un autre problème est que toutes les sous-classes qui oublient de remplacer clone () hériteront automatiquement de la copie superficielle par défaut, ce qui n'est probablement pas ce que vous voulez en cas d'état mutable (qui sera maintenant partagé entre la source et la copie).

La plupart des développeurs n'utilisent pas Cloneable pour ces raisons et implémentent simplement un constructeur de copie à la place.

Pour plus d'informations et les pièges potentiels de Cloneable, je recommande vivement le livre Effective Java de Joshua Bloch

Luke Hutteman
la source
12
  1. Le clonage invoque une manière extra-linguistique de construire des objets - sans constructeurs.
  2. Le clonage vous oblige à traiter en quelque sorte avec CloneNotSupportedException - ou à déranger le code client pour le traiter.
  3. Les avantages sont minimes - vous n'avez tout simplement pas besoin d'écrire manuellement un constructeur de copie.

Alors, utilisez Cloneable judicieusement. Cela ne vous donne pas suffisamment d'avantages par rapport aux efforts dont vous avez besoin pour tout faire correctement.

Vladimir Ivanov
la source
Comme l'a dit Bozho, n'utilisez pas Cloneable. À la place, utilisez un constructeur de copie. javapractices.com/topic/TopicAction.do?Id=12
Bane
@Bane, que se passe-t-il si vous ne connaissez pas le type de l'objet à cloner, comment sauriez-vous quel constructeur de copie de classe appeler?
Steve Kuo
@Steve: Je ne suis pas. Si vous allez cloner un objet, je suppose que vous savez déjà de quel type il s'agit - après tout, vous avez l'objet en main que vous prévoyez de cloner. Et s'il y a une situation où votre objet a perdu son type spécifique au profit d'un autre plus générique, ne pourriez-vous pas l'évaluer en utilisant une simple 'instance de' ???
Bane
4
@Bane: Supposons que vous ayez une liste d'objets tous dérivés du type A, peut-être avec 10 types différents. Vous ne savez pas quel est le type de chaque objet. Utiliser instanceof dans ce cas est une TRES mauvaise idée. Si vous ajoutez un autre type, chaque fois que vous faites cela, vous devrez ajouter une autre instance de test. Et si les classes dérivées se trouvent dans un autre package auquel vous ne pouvez même pas accéder? Le clonage est un modèle courant. Oui, l'implémentation java est mauvaise, mais il existe de nombreuses façons de contourner cela qui fonctionneront très bien. Un constructeur de copie n'est pas une opération équivalente.
Charles
@Charles: En l'absence d'exemple détaillé et en l'absence d'expérience récente dans la gestion de ce genre de problème, je vais devoir m'en remettre à Bloch. Élément n ° 11. C'est long et un peu difficile à lire, mais il dit essentiellement "éviter le clonage chaque fois que vous le pouvez, les constructeurs de copie sont votre ami".
Bane
7

Le clonage est un paradigme de programmation de base. Le fait que Java l'ait mal implémenté à bien des égards ne diminue en rien le besoin de clonage. Et, il est facile de mettre en œuvre le clonage qui fonctionnera comme vous le souhaitez, peu profond, profond, mixte, peu importe. Vous pouvez même utiliser le nom clone pour la fonction et ne pas implémenter Cloneable si vous le souhaitez.

Supposons que j'ai des classes A, B et C, où B et C sont dérivés de A. Si j'ai une liste d'objets de type A comme ceci:

ArrayList<A> list1;

Maintenant, cette liste peut contenir des objets de type A, B ou C. Vous ne savez pas de quel type sont les objets. Donc, vous ne pouvez pas copier la liste comme ceci:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Si l'objet est en fait de type B ou C, vous n'obtiendrez pas la bonne copie. Et si A était abstrait? Maintenant, certaines personnes ont suggéré ceci:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

C'est une très, très mauvaise idée. Que faire si vous ajoutez un nouveau type dérivé? Que faire si B ou C sont dans un autre package et que vous n'y avez pas accès dans cette classe?

Voici ce que vous aimeriez faire:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

De nombreuses personnes ont expliqué pourquoi l'implémentation Java de base du clone était problématique. Mais, il est facilement surmonté de cette façon:

En classe A:

public A clone() {
    return new A(this);
}

En classe B:

@Override
public B clone() {
    return new B(this);
}

En classe C:

@Override
public C clone() {
    return new C(this):
}

Je n'implémente pas Cloneable, en utilisant simplement le même nom de fonction. Si vous n'aimez pas ça, donnez-lui un autre nom.

Charles
la source
Je viens de voir cela après avoir répondu à votre commentaire dans une réponse séparée; Je vois où vous allez maintenant, mais 2 choses: 1) l'OP a demandé spécifiquement l'utilisation de Cloneable (pas sur le concept générique de clonage), et 2) vous vous fendez un peu les cheveux ici en essayant de faire la distinction entre un constructeur de copie et le concept générique de clonage. L'idée que vous transmettez ici est valide, mais à la racine, vous utilisez simplement un constructeur de copie. ;)
Bane
Bien que je veuille dire que je suis d'accord avec votre approche ici, celle d'inclure un A # copyMethod (), plutôt que de forcer l'utilisateur à appeler directement le constructeur de copie.
Bane
5

A) Il n'y a pas beaucoup d'avantages du clone par rapport à un constructeur de copie. Le plus important est probablement la possibilité de créer un nouvel objet du même type dynamique (en supposant que le type déclaré est clonable et possède une méthode de clonage public).

B) Le clone par défaut crée une copie superficielle, et elle restera une copie superficielle à moins que votre implémentation de clone ne change cela. Cela peut être difficile, surtout si votre classe a des champs finaux

Bozho a raison, le clone peut être difficile à obtenir. Un constructeur / usine de copie répondra à la plupart des besoins.

ILMTitan
la source
0

Quels sont les inconvénients de Cloneable?

Le clonage est très dangereux si l'objet que vous copiez a une composition.Vous devez penser aux effets secondaires possibles ci-dessous dans ce cas, car le clone crée une copie superficielle:

Supposons que vous ayez un objet pour gérer les manipulations liées à la base de données. Disons que cet objet a un Connectionobjet comme l'une des propriétés.

Alors , quand quelqu'un crée clone de originalObjectl'objet créé, nous allons dire cloneObject. Ici originalObjectet cloneObjectmaintenez la même référence pour Connectionobject.

Disons originalObjectferme l' Connectionobjet, donc maintenant le cloneObjectne fonctionnera pas parce que l' connectionobjet a été partagé entre eux et il a été effectivement fermé par le originalObject.

Un problème similaire peut se produire si vous voulez cloner un objet qui a IOStream comme propriété.

Comment se produit le clonage récursif si l'objet est un objet composite?

Cloneable effectue une copie superficielle. Cela signifie que les données de l'objet original et de l'objet clone pointeront vers la même référence / mémoire. au contraire dans le cas d'une copie profonde, les données de la mémoire de l'objet original sont copiées dans la mémoire de l'objet clone.

027
la source
Votre dernier paragraphe est très confus. Cloneablen'effectue pas de copie, Object.clonefait. "Les données de la mémoire de l'objet original sont copiées dans la mémoire de l'objet clone" est précisément ce que Object.clonefait. Vous devez parler de la mémoire des objets référencés pour décrire la copie profonde.
aioobe