2008-09-15 7 views
226

En Java, il est un peu difficile d'implémenter une fonction de copie d'objets en profondeur. Quelles mesures prenez-vous pour vous assurer que l'objet original et le clone ne partagent aucune référence?Comment faites-vous une copie en profondeur d'un objet dans Java?

+4

Kryo a un support intégré pour [copier/clonage] (https://code.google.com/p/kryo/#Copying/cloning). C'est une copie directe d'un objet à l'autre, pas d'objet-> octets-> objet. – NateS

+1

Voici une question connexe qui a été posée plus tard: [recommandation d'utilitaire de clonage profond] (http://stackoverflow.com/q/665860/152061) –

Répondre

135

Un moyen sûr est de sérialiser l'objet, puis de le désérialiser. Cela garantit que tout est une référence nouvelle.

Here's an article sur la façon de le faire efficacement. Avertissements: Il est possible que les classes remplacent la sérialisation, de sorte que les nouvelles instances ne soient pas créées par, par ex. pour les singletons. Aussi, bien sûr, cela ne fonctionne pas si vos classes ne sont pas sérialisables.

+5

Sachez que l'implémentation FastByteArrayOutputStream fournie dans l'article pourrait être plus efficace. Il utilise une extension ArrayList-style lorsque le tampon se remplit, mais il est préférable d'utiliser une approche d'expansion de style LinkedList. Au lieu de créer un nouveau tampon 2x et de mémoriser le tampon courant, maintenez une liste chaînée de tampons, en ajoutant un nouveau tampon lorsque le courant se remplit. Si vous obtenez une requête pour écrire plus de données que ne le ferait votre taille de tampon par défaut, créez un nœud tampon exactement aussi grand que la requête; les nœuds n'ont pas besoin d'être de la même taille. –

+0

Il suffit d'utiliser kryo: https://github.com/EsotericSoftware/kryo#copyingcloning benchmark http://www.slideshare.net/AlexTumanoff/serialization-and-performance – zengr

+0

Un bon article, qui explique la copie profonde grâce à la sérialisation: http : //www.javaworld.com/article/2077578/learn-java/java-tip-76--an-alternative-to-the-deep-copy-technique.html –

6

La copie en profondeur ne peut être faite qu'avec le consentement de chaque classe. Si vous avez le contrôle sur la hiérarchie des classes, vous pouvez implémenter l'interface clonable et implémenter la méthode Clone. Sinon, il est impossible de faire une copie en profondeur car l'objet peut également partager des ressources non-données (par exemple des connexions à une base de données). En général, cependant, la copie en profondeur est considérée comme une mauvaise pratique dans l'environnement Java et doit être évitée via les pratiques de conception appropriées.

+1

Pourriez-vous décrire les "pratiques de conception appropriées"? – eftokay83

32

Vous pouvez effectuer un clonage profond basé sur la sérialisation en utilisant org.apache.commons.lang3.SerializationUtils.clone(T) dans Commons Lang, mais soyez prudent: les performances sont abyssales.

En général, il est recommandé d'écrire vos propres méthodes de clonage pour chaque classe d'un objet dans le graphe d'objet nécessitant un clonage.

8

Utilisez XStream (http://x-stream.github.io/). Vous pouvez même contrôler les propriétés que vous pouvez ignorer via des annotations ou spécifier explicitement le nom de la propriété dans la classe XStream. De plus, vous n'avez pas besoin d'implémenter l'interface clonable.

11

XStream est vraiment utile dans de tels cas. Voici un code simple clonage

private static final XStream XSTREAM = new XStream(); 
... 

Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj)); 
+0

Nooo, vous n'avez pas besoin de la surcharge de xml-ing l'objet. – egelev

+0

@egeleve Vous réalisez que vous répondez à un commentaire de '08 non? Je n'utilise plus Java et il y a probablement de meilleurs outils maintenant. Cependant à ce moment-là, sérialiser à un format différent et sérialiser de nouveau a semblé comme un bon hack - c'était vraiment inefficace. – sankara

57

Quelques personnes ont mentionné l'utilisation ou Object.clone() primordiale. Ne fais pas ça. Object.clone() a quelques problèmes majeurs, et son utilisation est déconseillée dans la plupart des cas. S'il vous plaît voir le point 11, de "Effective Java" par Joshua Bloch pour une réponse complète. Je crois que vous pouvez utiliser en toute sécurité Object.clone() sur des tableaux de types primitifs, mais en dehors de cela, vous devez être judicieux sur l'utilisation correcte et le remplacement de clone.

Les schémas qui s'appuient sur la sérialisation (XML ou autre) sont kludgy.

Il n'y a pas de réponse facile ici. Si vous voulez copier en profondeur un objet, vous devrez traverser le graphe d'objet et copier explicitement chaque objet enfant via le constructeur de copie de l'objet ou une méthode statique qui copiera à son tour l'objet enfant. Les biens immuables (par exemple String s) n'ont pas besoin d'être copiés. En aparté, vous devriez privilégier l'immuabilité pour cette raison.

6
import com.thoughtworks.xstream.XStream; 

public class deepCopy { 
    private static XStream xstream = new XStream(); 

    //serialize with Xstream them deserialize ... 
    public static Object deepCopy(Object obj){ 
     return xstream.fromXML(xstream.toXML(obj)); 
    } 
} 
44

Vous pouvez effectuer une copie en profondeur avec la sérialisation sans créer de fichiers.

L'objet que vous souhaitez copier en profondeur doit être implement serializable. Si la classe n'est pas définitive ou ne peut pas être modifiée, étendez la classe et implémentez sérialisable.

Convertir votre classe à un flux d'octets:

ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
ObjectOutputStream oos = new ObjectOutputStream(bos); 
oos.writeObject(object); 
oos.flush(); 
oos.close(); 
bos.close(); 
byte[] byteData = bos.toByteArray(); 

Restaurez votre classe à partir d'un flux d'octets:

ByteArrayInputStream bais = new ByteArrayInputStream(byteData); 
(Object) object = (Object) new ObjectInputStream(bais).readObject(); 
+4

Si la classe est finale, comment l'étendriez-vous? –

+1

@KumarManish class MyContainer implémente Serializable {Instance MyFinalClass; ...} –

+0

Je trouve cela une excellente réponse. Clone est un désordre – blackbird014

22

Une façon de mettre en œuvre copie en profondeur est d'ajouter des constructeurs de copie à chaque associé classe. Un constructeur de copie prend une instance de 'this' comme argument unique et en copie toutes les valeurs. Un peu de travail, mais assez simple et sûr.

EDIT: notez que vous n'avez pas besoin d'utiliser les méthodes accesseur pour lire les champs. Vous pouvez accéder directement à tous les champs car l'instance source est toujours du même type que l'instance avec le constructeur de copie. Évident mais pourrait être négligé.

Exemple:

public class Order { 

    private long number; 

    public Order() { 
    } 

    /** 
    * Copy constructor 
    */ 
    public Order(Order source) { 
     number = source.number; 
    } 
} 


public class Customer { 

    private String name; 
    private List<Order> orders = new ArrayList<Order>(); 

    public Customer() { 
    } 

    /** 
    * Copy constructor 
    */ 
    public Customer(Customer source) { 
     name = source.name; 
     for (Order sourceOrder : source.orders) { 
      orders.add(new Order(sourceOrder)); 
     } 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 
} 

Edit: Notez que lors de l'utilisation des constructeurs de copie vous devez connaître le type d'exécution de l'objet que vous copiez. Avec l'approche ci-dessus, vous ne pouvez pas facilement copier une liste mixte (vous pourriez être capable de le faire avec un code de réflexion).

+1

Juste intéressé dans le cas que ce que vous copiez est une sous-classe, mais est référencé par le parent. Est-il possible de surcharger le constructeur de copie? –

+0

Pourquoi votre classe parente fait-elle référence à sa sous-classe? Pouvez-vous donner un exemple? –

+1

Classe publique Voiture étend Véhicule Puis se référant à la voiture comme un véhicule. originaList = nouveau ArrayList ; copyList = new ArrayList ; originalList.add (new Car()); pour (Véhicule véhicule: liste véhicules) { copyList.add (véhicule neuf (véhicule)); } –

12

Vous pouvez use a library qui a une API simple, et effectue le clonage relativement rapide avec la réflexion (devrait être plus rapide que les méthodes de sérialisation).

Cloner cloner = new Cloner(); 

MyClass clone = cloner.deepClone(o); 
// clone is a deep-clone of o 
4

I utilisé pour le clonage Dozer objets java et il est très bien à cela, la bibliothèque Kryo est une autre alternative.

14

Apache commons offre un moyen rapide de cloner en profondeur un objet.

My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1); 
+0

upvote pour suggérer une bibliothèque testée et stable – katzenhut

2

BeanUtils fait un très bon haricots de clonage profond d'emploi.

BeanUtils.cloneBean(obj); 
+2

Il fait peu de clonage. –

2

1)

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; 
    } 
} 

2) 

    // (1) create a MyPerson object named Al 
    MyAddress address = new MyAddress("Vishrantwadi ", "Pune", "India"); 
    MyPerson al = new MyPerson("Al", "Arun", address); 

    // (2) make a deep clone of Al 
    MyPerson neighbor = (MyPerson)deepClone(al); 

Voici votre myPerson et la classe MyAddress doivent mettre en œuvre l'interface serilazable

3

Pour les objets complexes et lorsque les performances ne sont pas significatifs i utiliser une bibliothèque JSON, comme gson sérialiser l'objet au texte json, puis désérialiser le texte pour obtenir un nouvel objet.

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

public static <T> T Copy(T AnObject, Class<T> ClassInfo) 
{ 
    Gson gson = new GsonBuilder().create(); 
    String text = gson.toJson(AnObject); 
    T newObject = gson.fromJson(text, ClassInfo); 
    return newObject; 
} 
public static void main(String[] args) 
{ 
    String originalObject = "hello"; 
    String copiedObject = Copy(originalObject, String.class); 

} 
+1

Veuillez respecter les conventions de nommage Java pour votre propre et pour notre bien. –

4

Pour Spring Framework utilisateurs.En utilisant la classe org.springframework.util.SerializationUtils:

@SuppressWarnings("unchecked") 
public static <T extends Serializable> T clone(T object) { 
    return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object)); 
} 
Questions connexes