Java: solution recommandée pour le clonage profond / la copie d'une instance

177

Je me demande s'il existe une méthode recommandée pour effectuer un clonage / copie en profondeur d'une instance en java.

J'ai 3 solutions en tête, mais j'en ai raté quelques-unes, et j'aimerais avoir votre avis

modifier: inclure la proposition de Bohzo et affiner la question: il s'agit plus de clonage profond que de clonage superficiel.

Fais le toi-même:

coder le clone à la main les propriétés après les propriétés et vérifier que les instances mutables sont également clonées.
pro:
- contrôle de ce qui sera effectué
- exécution rapide
contre:
- fastidieux à écrire et à maintenir
- sujet aux bogues (échec copier / coller, propriété manquante, propriété mutable réaffectée)

Utilisez la réflexion:

Avec vos propres outils de réflexion ou avec un assistant externe (comme jakarta common-beans), il est facile d'écrire une méthode de copie générique qui fera le travail en une seule ligne.
pro:
- facile à écrire
- pas de maintenance
contre:
- moins de contrôle sur ce qui se passe
- sujet aux bugs avec objet mutable si l'outil de réflexion ne clone pas trop les sous-objets
- exécution plus lente

Utilisez le cadre de clonage:

Utilisez un framework qui le fait pour vous, comme:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

pro:
- identique à la réflexion
- plus de contrôle sur ce qui sera exactement cloné.
inconvénients:
- chaque instance mutable est entièrement clonée, même à la fin de la hiérarchie
- peut être très lente à exécuter

Utiliser l'instrumentation bytecode pour écrire un clone au moment de l'exécution

javassit , BCEL ou cglib peuvent être utilisés pour générer un cloneur dédié aussi vite qu'une écriture manuelle. Quelqu'un connaît une bibliothèque utilisant l'un de ces outils à cette fin?

Qu'est-ce que j'ai manqué ici?
Lequel recommanderiez-vous?

Merci.

Guillaume
la source
1
apparemment Java Deep Cloning Library a été déplacé ici: code.google.com/p/cloning
Mr_and_Mrs_D

Réponses:

155

Pour le clonage profond (clone la totalité de la hiérarchie d'objets):

  • commons-lang SerializationUtils - en utilisant la sérialisation - si toutes les classes sont sous votre contrôle et que vous pouvez forcer l'implémentation Serializable.

  • Java Deep Cloning Library - en utilisant la réflexion - dans les cas où les classes ou les objets que vous souhaitez cloner sont hors de votre contrôle (une bibliothèque tierce) et que vous ne pouvez pas les faire implémenter Serializable, ou dans les cas où vous ne souhaitez pas implémenter Serializable.

Pour le clonage superficiel (ne clone que les propriétés de premier niveau):

J'ai délibérément omis l'option "do-it-yourself" - les API ci-dessus fournissent un bon contrôle sur ce qu'il faut et ne pas cloner (par exemple en utilisant transient, ou String[] ignoreProperties), donc réinventer la roue n'est pas préférable.

Bozho
la source
Merci Bozho, c'est précieux. Et je suis d'accord avec vous sur l'option DIY! Avez-vous déjà essayé la sérialisation des communs et / ou la bibliothèque de clonage profond? Et les perfs?
Guillaume
oui, j'ai utilisé toutes les options ci-dessus, pour les raisons ci-dessus :) seule la bibliothèque de clonage avait des problèmes lorsque les proxys CGLIB étaient impliqués et manquait certaines fonctionnalités souhaitées, mais je pense que cela devrait être corrigé maintenant.
Bozho
Hé, si mon entité est attachée et que j'ai des choses paresseuses, SerializationUtils vérifie-t-il la base de données pour les propriétés paresseuses? Parce que c'est ce que je veux, et ce n'est pas le cas!
Cosmin Cosmin
si vous avez une session active - oui, c'est le cas.
Bozho le
@Bozho Donc, vous voulez dire que si tous les objets dans le bean implémentent sérialisable, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) fera une copie complète?
sautez le
36

Le livre de Joshua Bloch a un chapitre entier intitulé "Point 10: Ignorer le clone de manière judiciaire" dans lequel il explique pourquoi le remplacement du clone est pour la plupart une mauvaise idée parce que la spécification Java crée de nombreux problèmes.

Il propose quelques alternatives:

  • Utilisez un modèle de fabrique à la place d'un constructeur:

         public static Yum newInstance(Yum yum);
  • Utilisez un constructeur de copie:

         public Yum(Yum yum);

Toutes les classes de collection en Java supportent le constructeur de copie (par exemple new ArrayList (l);)

LeWoody
la source
1
D'accord. Dans mon projet, j'ai défini une Copyableinterface contenant une getCopy()méthode. Utilisez simplement le modèle de prototype manuellement.
gpampara
Eh bien, je ne demandais pas l'interface clonable, mais comment effectuer une opération de clonage / copie en profondeur. Avec un constructeur ou une fabrique, vous devez toujours créer votre nouvelle instance à partir de votre source.
Guillaume
@Guillaume Je pense que vous devez être prudent en utilisant les mots clone / copie profonds. Cloner et copier en java ne signifient PAS la même chose. La spécification Java a plus à dire à ce sujet ... Je pense que vous voulez une copie complète de ce que je peux dire.
LeWoody
OK La spécification Java est précise sur ce qu'est un clone ... Mais nous pouvons aussi parler du clone dans un sens plus courant ... Par exemple, l'une des lib recommandées par bohzo s'appelle 'Java Deep Cloning Library' ...
Guillaume le
2
@LWoodyiii cette newInstance()méthode et le Yumconstructeur feraient une copie profonde ou une copie superficielle?
Geek
10

Depuis la version 2.07, Kryo prend en charge le clonage superficiel / profond :

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

Kryo est rapide, sur leur page, vous trouverez une liste des entreprises qui l'utilisent en production.

Andrey Chaschev
la source
5

Utilisez XStream toXML / fromXML en mémoire. Extrêmement rapide et existe depuis longtemps et va fort. Les objets n'ont pas besoin d'être sérialisables et vous n'avez pas de réflexion d'utilisation (bien que XStream le fasse). XStream peut discerner les variables qui pointent vers le même objet et ne pas faire accidentellement deux copies complètes de l'instance. Beaucoup de détails comme celui-là ont été élaborés au fil des ans. Je l'utilise depuis un certain nombre d'années et c'est un incontournable. C'est à peu près aussi facile à utiliser que vous pouvez l'imaginer.

new XStream().toXML(myObj)

ou

new XStream().fromXML(myXML)

Cloner,

new XStream().fromXML(new XStream().toXML(myObj))

Plus succinctement:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));
Ranx
la source
3

Pour les objets compliqués et lorsque les performances ne sont pas significatives, j'utilise gson pour sérialiser l'objet en texte json, puis désérialise le texte pour obtenir un nouvel objet.

gson qui basé sur la réflexion fonctionnera dans la plupart des cas, sauf que les transientchamps ne seront pas copiés et les objets avec une référence circulaire avec cause StackOverflowError.

public static <ObjectType> ObjectType Copy(ObjectType AnObject, Class<ObjectType> ClassInfo)
{
    Gson gson = new GsonBuilder().create();
    String text = gson.toJson(AnObject);
    ObjectType newObject = gson.fromJson(text, ClassInfo);
    return newObject;
}
public static void main(String[] args)
{
    MyObject anObject ...
    MyObject copyObject = Copy(o, MyObject.class);

}
tibou
la source
2

Dépend.

Pour la vitesse, utilisez le bricolage. Pour pare-balles, utilisez la réflexion.

BTW, la sérialisation n'est pas la même chose que refl, car certains objets peuvent fournir des méthodes de sérialisation écrasées (readObject / writeObject) et ils peuvent être bogués

Yoni Roit
la source
1
la réflexion n'est pas à l'épreuve des balles: elle peut conduire dans certaines situations où votre objet cloné fait référence à votre source ... Si la source change, le clone changera aussi!
Guillaume
1

Je recommanderais la méthode DIY qui, combinée à une bonne méthode hashCode () et equals (), devrait être facile à prouver dans un test unitaire.

Dominik Sandjaja
la source
eh bien, le paresseux me déchaîne beaucoup lors de la création d'un tel code factice. Mais cela ressemble à la voie la plus sage ...
Guillaume
2
désolé, mais le bricolage est la voie à suivre SEULEMENT si aucune autre solution ne vous convient ... ce qui n'est presque jamais
Bozho
1

Je suggère de remplacer Object.clone (), d'appeler d'abord super.clone () et d'appeler ref = ref.clone () sur toutes les références que vous souhaitez copier en profondeur. C'est plus ou moins une approche Do it yourself mais nécessite un peu moins de codage.

x4u
la source
2
C'est l'un des nombreux problèmes de la méthode de clonage (cassé): dans une hiérarchie de classes, vous devez toujours appeler super.clone (), ce qui peut facilement être oublié, c'est pourquoi je préférerais utiliser un constructeur de copie.
helpermethod
0

Pour le clonage profond, implémentez Serializable sur chaque classe que vous souhaitez cloner comme ceci

public static class Obj implements Serializable {
    public int a, b;
    public Obj(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

Et puis utilisez cette fonction:

public static Object deepClone(Object object) {
    try {
        ByteArrayOutputStream baOs = new ByteArrayOutputStream();
        ObjectOutputStream oOs = new ObjectOutputStream(baOs);
        oOs.writeObject(object);
        ByteArrayInputStream baIs = new ByteArrayInputStream(baOs.toByteArray());
        ObjectInputStream oIs = new ObjectInputStream(baIs);
        return oIs.readObject();
    }
    catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

comme ça: Obj newObject = (Obj)deepClone(oldObject);

Alexander Maslew
la source