Comment copier un objet en Java?

794

Considérez le code ci-dessous:

DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // prints 'foo'

DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // prints 'foo'

dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // prints 'bar' but it should print 'foo'

Donc, je veux copier le dumà dumtwoet le changement dumsans affecter la dumtwo. Mais le code ci-dessus ne fait pas cela. Quand je change quelque chose dum, le même changement se produit dumtwoégalement.

Je suppose que quand je dis dumtwo = dum, Java copie uniquement la référence . Alors, existe-t-il un moyen de créer une nouvelle copie de dumet de l'affecter à dumtwo?

Veera
la source

Réponses:

611

Créez un constructeur de copie:

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}

Chaque objet a également une méthode de clonage qui peut être utilisée pour copier l'objet, mais ne l'utilisez pas. Il est beaucoup trop facile de créer une classe et de faire une méthode de clonage incorrecte. Si vous voulez faire cela, lisez au moins ce que Joshua Bloch a à dire à ce sujet dans Effective Java .

egaga
la source
45
Mais alors il devrait changer son code en DummyBean deux = nouveau DummyBean (un); Droite?
Chris K
12
Cette méthode accomplit-elle efficacement la même chose qu'une copie complète?
Matthew Piziak
124
@MatthewPiziak, pour moi - ce ne serait pas un clone profond car tous les objets imbriqués référenceraient toujours l'instance source d'origine, pas un doublon à moins que chaque objet de référence (type sans valeur) fournisse le même modèle de constructeur que ci-dessus.
SliverNinja - MSFT
17
@Timmmm: Oui, ils référenceront la même chaîne mais parce qu'elle est immuable, c'est ok. Il en va de même pour les primitives. Pour les non-primitifs, vous ne feriez que copier l'appel de constructeur de manière récursive. Par exemple, si DummyBean a référencé FooBar, alors FooBar devrait avoir un constructeur FooBar (FooBar another), et le mannequin devrait appeler this.foobar = new FooBar (another.foobar)
egaga
7
@ChristianVielma: Non, ce ne sera pas "johndoe". Comme l'a dit Timmmm, la chaîne elle-même est immuable. Avec un, setDummy (..) vous définissez la référence en un pour pointer sur "johndoe", mais pas celui en un.
keuleJ
404

Basique: copie d'objets en Java.

Supposons un objet- obj1, qui contient deux objets, ContenuObj1 et ContenuObj2 .
entrez la description de l'image ici

copie superficielle: la copie
superficielle crée une nouvelle instanceclasse de la même classe et copie tous les champs dans la nouvelle instance et la renvoie. La classe d'objets fournit une cloneméthode et prend en charge la copie superficielle.
entrez la description de l'image ici

Copie profonde:
une copie profonde se produit lorsqu'un objet est copié avec les objets auxquels il se réfère . L'image ci-dessous montre obj1qu'une copie complète a été effectuée dessus. Non seulement a obj1été copié , mais les objets qu'il contient ont également été copiés. Nous pouvons utiliser Java Object Serializationpour faire une copie complète. Malheureusement, cette approche pose également certains problèmes ( exemples détaillés ).
entrez la description de l'image ici

Problèmes possibles:
clone est difficile à mettre en œuvre correctement.
Il est préférable d'utiliser la copie défensive , les constructeurs de copie (comme réponse @egaga) ou les méthodes d'usine statiques .

  1. Si vous avez un objet, que vous savez avoir une clone()méthode publique , mais que vous ne connaissez pas le type de l'objet au moment de la compilation, vous avez un problème. Java a une interface appelée Cloneable. En pratique, nous devons implémenter cette interface si nous voulons créer un objet Cloneable. Object.cloneest protégé , nous devons donc le remplacer par une méthode publique pour qu'il soit accessible.
  2. Un autre problème se pose lorsque nous essayons de copier en profondeur un objet complexe . Supposons que la clone()méthode de toutes les variables d'objet membre effectue également une copie en profondeur, cela est trop risqué pour une hypothèse. Vous devez contrôler le code dans toutes les classes.

Par exemple, org.apache.commons.lang.SerializationUtils aura une méthode pour Deep clone utilisant la sérialisation ( Source ). Si nous devons cloner Bean, il existe deux méthodes utilitaires dans org.apache.commons.beanutils ( Source ).

  • cloneBean clonera un bean en fonction des getters et setters de propriété disponibles, même si la classe de bean elle-même n'implémente pas Cloneable.
  • copyProperties copiera les valeurs de propriété du bean d'origine vers le bean de destination dans tous les cas où les noms de propriété sont identiques.
Chandra Sekhar
la source
1
Pouvez-vous expliquer ce qu'est un objet contenu dans un autre?
Freakyuser
1
@Chandra Sekhar "la copie superficielle crée une nouvelle instance de la même classe et copie tous les champs dans la nouvelle instance et la renvoie" c'est faux de mentionner tous les champs, les objets bcz ne sont pas copiés seulement les références sont copiées qui pointe vers le même objet que l'ancien (original) pointait.
JAVA
4
@sunny - La description de Chandra est correcte. Et ainsi est votre description de ce qui se passe; Je dis que vous avez une mauvaise compréhension de la signification de "copie tous les champs". Le champ est la référence, ce n'est pas l'objet auquel il est fait référence. "copier tous les champs" signifie "copier toutes ces références". Il est bon que vous ayez souligné ce que cela signifie exactement, pour quiconque a la même mauvaise interprétation que vous, de la phrase "copier tous les champs". :)
ToolmakerSteve
2
... si nous pensons en termes de langage OO de niveau inférieur, avec des "pointeurs" vers des objets, un tel champ contiendrait l'adresse en mémoire (telle que "0x70FF1234") à laquelle les données d'objet sont trouvées. Cette adresse est la "valeur de champ" copiée (attribuée). Vous avez raison, le résultat final est que les deux objets ont des champs qui font référence au (même point) au même objet.
ToolmakerSteve
127

Dans le package, import org.apache.commons.lang.SerializationUtils;il y a une méthode:

SerializationUtils.clone(Object);

Exemple:

this.myObjectCloned = SerializationUtils.clone(this.object);
pacheco
la source
59
Tant que l'objet implémenteSerializable
Androiderson
2
Dans ce cas, l'objet cloné n'a aucune référence à l'original, si le dernier est statique.
Dante
8
Une bibliothèque tierce juste pour cloner un objet!
Khan
2
@Khan, "une bibliothèque tierce juste pour" est une discussion entièrement distincte! : D
Charles Wood
103

Suivez juste comme ci-dessous:

public class Deletable implements Cloneable{

    private String str;
    public Deletable(){
    }
    public void setStr(String str){
        this.str = str;
    }
    public void display(){
        System.out.println("The String is "+str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

et partout où vous voulez obtenir un autre objet, effectuez simplement le clonage. par exemple:

Deletable del = new Deletable();
Deletable delTemp = (Deletable ) del.clone(); // this line will return you an independent
                                 // object, the changes made to this object will
                                 // not be reflected to other object
Bhasker Tiwari
la source
1
Avez-vous testé cela? Je pourrais l'utiliser pour mon projet et il est important d'être correct.
brumeux
2
@misty Je l'ai testé. Fonctionne parfaitement sur mon application de production
Andrii Kovalchuk
Après le clonage, lorsque vous modifiez l'objet d'origine, il modifie également le clone.
Sibish
4
C'est faux car ce n'est pas une copie complète qui a été demandée.
Bluehorn
1
Cette méthode clone le pointeur qui pointe vers l'objet clonable, mais toutes les propriétés à l'intérieur des deux objets sont les mêmes, il y a donc un nouvel objet créé dans la mémoire, mais les données à l'intérieur de chaque objet sont les mêmes données de la mémoire
Omar HossamEldin
40

Pourquoi n'y a-t-il pas de réponse pour utiliser l'API Reflection?

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                field.set(clone, field.get(obj));
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

C'est vraiment simple.

EDIT: inclure un objet enfant via la récursivité

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                if(field.get(obj) == null || Modifier.isFinal(field.getModifiers())){
                    continue;
                }
                if(field.getType().isPrimitive() || field.getType().equals(String.class)
                        || field.getType().getSuperclass().equals(Number.class)
                        || field.getType().equals(Boolean.class)){
                    field.set(clone, field.get(obj));
                }else{
                    Object childObj = field.get(obj);
                    if(childObj == obj){
                        field.set(clone, clone);
                    }else{
                        field.set(clone, cloneObject(field.get(obj)));
                    }
                }
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }
WillingLearner
la source
Cela semble beaucoup mieux, mais vous devez seulement considérer les champs finaux car setAccessible (true) peut échouer, donc vous devrez peut-être gérer séparément l'exception IllegalAccessException levée lors de l'appel de field.set (clone, field.get (obj)) séparément.
Max
1
Je l'ai tellement aimé, mais pouvez-vous le refactoriser pour utiliser des génériques? private static <T> T cloneObject (T obj) {....}
Adelin
2
Je pense que c'est un problème lorsque nous avons des références de propriétés à ses parents: Class A { B child; } Class B{ A parent; }
nhthai
Il échoue même dans cette situation, doit être géré, je vais jouer avec lui demain. class car { car car = new car(); }
Ján Яabčan
2
Ceci est sujet aux erreurs. Je ne sais pas comment cela va gérer les collections
ACV
31

J'utilise la bibliothèque JSON de Google pour la sérialiser, puis je crée une nouvelle instance de l'objet sérialisé. Il copie en profondeur avec quelques restrictions:

  • il ne peut pas y avoir de références récursives

  • il ne copiera pas les tableaux de types disparates

  • les tableaux et les listes doivent être tapés ou il ne trouvera pas la classe à instancier

  • vous devrez peut-être encapsuler des chaînes dans une classe que vous vous déclarez

J'utilise également cette classe pour enregistrer les préférences utilisateur, les fenêtres et autres éléments à recharger au moment de l'exécution. Il est très simple d'utilisation et efficace.

import com.google.gson.*;

public class SerialUtils {

//___________________________________________________________________________________

public static String serializeObject(Object o) {
    Gson gson = new Gson();
    String serializedObject = gson.toJson(o);
    return serializedObject;
}
//___________________________________________________________________________________

public static Object unserializeObject(String s, Object o){
    Gson gson = new Gson();
    Object object = gson.fromJson(s, o.getClass());
    return object;
}
       //___________________________________________________________________________________
public static Object cloneObject(Object o){
    String s = serializeObject(o);
    Object object = unserializeObject(s,o);
    return object;
}
}
Peter
la source
Cela fonctionne très bien. Mais attention si vous essayez de cloner quelque chose comme List <Integer>. Ce sera buggé, mes entiers se sont transformés en doubles, 100,0. Cela m'a pris du temps pour comprendre pourquoi ils sont comme ça. La solution était de cloner les entiers un par un et de les ajouter à la liste dans un cycle.
paakjis
24

Oui, vous faites simplement référence à l'objet. Vous pouvez cloner l'objet s'il implémente Cloneable.

Consultez cet article wiki sur la copie d'objets.

Reportez-vous ici: Copie d'objets

Chrisb
la source
14

Ajouter Cloneableet ci-dessous du code à votre classe

public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

Utilisez ceci clonedObject = (YourClass) yourClassObject.clone();

Teja Maridu
la source
12

Oui. Vous devez copier en profondeur votre objet.

bruno conde
la source
1
En l'état, ce n'est même pas du tout une copie.
Michael Myers
C'est probablement la réponse la moins utile que j'ai vue sur stackoverflow.
Cyril
12

Cela fonctionne aussi. En supposant un modèle

class UserAccount{
   public int id;
   public String name;
}

Ajoutez d'abord compile 'com.google.code.gson:gson:2.8.1'à votre application> gradle & sync. alors

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);

Vous pouvez exclure l'utilisation d'un champ à l'aide d'un transientmot clé après le modificateur d'accès.

Remarque: il s'agit d'une mauvaise pratique. Ne recommande pas non plus d'utiliser Cloneableou JavaSerializationc'est lent et cassé. Écrire le constructeur de copie pour les meilleures performances réf .

Quelque chose comme

class UserAccount{
        public int id;
        public String name;
        //empty constructor
        public UserAccount(){}
        //parameterize constructor
        public UserAccount(int id, String name) {
            this.id = id;
            this.name = name;
        }

        //copy constructor
        public UserAccount(UserAccount in){
            this(in.id,in.name);
        }
    }

Statistiques de test de 90000 itérations: la
ligne UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);dure 808 ms

La ligne UserAccount clone = new UserAccount(aO);prend moins de 1 ms

Conclusion: utilisez gson si votre patron est fou et que vous préférez la vitesse. Utilisez le deuxième constructeur de copie si vous préférez la qualité.

Vous pouvez également utiliser le plugin générateur de code de constructeur de copie dans Android Studio.

Qamar
la source
Pourquoi l'avez-vous suggéré si c'est une mauvaise pratique?
Parth Mehrotra du
Merci @ParthMehrotra désormais amélioré
Qamar
9

Utilisez un utilitaire de clonage en profondeur:

SomeObjectType copy = new Cloner().deepClone(someObject);

Cela copiera en profondeur tout objet java, vérifiez-le sur https://github.com/kostaskougios/cloning

Cojones
la source
1
n'a pas fonctionné pour moi en utilisant une classe personnalisée. obtention de l'exception suivante: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker
9

Le clonage en profondeur est votre réponse, qui nécessite la mise en œuvre de l' Cloneableinterface et le remplacement de la clone()méthode.

public class DummyBean implements Cloneable {

   private String dummy;

   public void setDummy(String dummy) {
      this.dummy = dummy;
   }

   public String getDummy() {
      return dummy;
   }

   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean)super.clone();
      cloned.setDummy(cloned.getDummy());
      // the above is applicable in case of primitive member types like String 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

Tu l'appelleras comme ça DummyBean dumtwo = dum.clone();

abbas
la source
2
dummy, a String, est immuable, vous n'avez pas besoin de le copier
Steve Kuo
7

Pour ce faire, vous devez cloner l'objet d'une manière ou d'une autre. Bien que Java ait un mécanisme de clonage, ne l'utilisez pas si vous n'en avez pas besoin. Créez une méthode de copie qui effectue le travail de copie pour vous, puis procédez comme suit:

dumtwo = dum.copy();

Voici quelques conseils supplémentaires sur différentes techniques pour réaliser une copie.

Yishai
la source
6

Autre que la copie explicite, une autre approche consiste à rendre l'objet immuable (aucune setou d'autres méthodes de mutation). De cette façon, la question ne se pose jamais. L'immuabilité devient plus difficile avec des objets plus grands, mais cet autre aspect est qu'elle vous pousse dans la direction de la division en petits objets cohérents et composites.

Tom Hawtin - sellerie
la source
5

Alternative à la méthode de copie du constructeur egaga . Vous avez probablement déjà un POJO, alors ajoutez simplement une autre méthode copy()qui retourne une copie de l'objet initialisé.

class DummyBean {
    private String dummyStr;
    private int dummyInt;

    public DummyBean(String dummyStr, int dummyInt) {
        this.dummyStr = dummyStr;
        this.dummyInt = dummyInt;
    }

    public DummyBean copy() {
        return new DummyBean(dummyStr, dummyInt);
    }

    //... Getters & Setters
}

Si vous en avez déjà un DummyBeanet que vous souhaitez en obtenir une copie:

DummyBean bean1 = new DummyBean("peet", 2);
DummyBean bean2 = bean1.copy(); // <-- Create copy of bean1 

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

//Change bean1
bean1.setDummyStr("koos");
bean1.setDummyInt(88);

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

Production:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Mais les deux fonctionnent bien, c'est à vous de décider ...

Pierre
la source
3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}
Mahdi Abdi
la source
3

Vous pouvez copier en profondeur automatiquement avec XStream, depuis http://x-stream.github.io/ :

XStream est une bibliothèque simple pour sérialiser des objets en XML et inversement.

Ajoutez-le à votre projet (si vous utilisez maven)

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.3.1</version>                
</dependency>

alors

DummyBean dum = new DummyBean();
dum.setDummy("foo");
DummyBean dumCopy = (DummyBean) XSTREAM.fromXML(XSTREAM.toXML(dum));

Avec cela, vous avez une copie sans avoir besoin d'implémenter une interface de clonage.

Jaime Hablutzel
la source
29
La conversion vers / depuis XML ne semble pas très ... élégante. Pour le dire doucement!
Timmmm
Jetez un œil à java.beans.XMLEncoderune API Java standard qui se sérialise également en XML (mais pas précisément à des fins de copie approfondie).
Jaime Hablutzel
1
vous rendez-vous compte à quel point c'est lourd?
mahieddine
1
Beaucoup de frais généraux à mon avis, car vous devez ajouter une bibliothèque tierce et effectuer une sérialisation d'objet qui a très probablement un impact énorme sur les performances.
NiThDi
2
public class MyClass implements Cloneable {

private boolean myField= false;
// and other fields or objects

public MyClass (){}

@Override
public MyClass clone() throws CloneNotSupportedException {
   try
   {
       MyClass clonedMyClass = (MyClass)super.clone();
       // if you have custom object, then you need create a new one in here
       return clonedMyClass ;
   } catch (CloneNotSupportedException e) {
       e.printStackTrace();
       return new MyClass();
   }

  }
}

et dans votre code:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();
Amir Hossein Ghasemi
la source
2
Il n'y a aucun point dans l'ensemble "jette CloneNotSupportedException" dans la déclaration si vous essayez d'intercepter l'exception et n'est pas levé. Donc, vous pouvez simplement le supprimer.
Christian
2

Passez l'objet que vous souhaitez copier et obtenez l'objet que vous souhaitez:

private Object copyObject(Object objSource) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(objSource);
            oos.flush();
            oos.close();
            bos.close();
            byte[] byteData = bos.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
            try {
                objDest = new ObjectInputStream(bais).readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return objDest;

    }

Maintenant, analysez l' objDestobjet souhaité.

Codage heureux!

A-Droid Tech
la source
1

Vous pouvez essayer d'implémenter Cloneableet d'utiliser la clone()méthode; cependant, si vous utilisez la méthode de clonage, vous devez - par défaut - toujours remplacer Objectla public Object clone()méthode de.


la source
1

Si vous pouvez ajouter une annotation au fichier source, un processeur d'annotation ou un générateur de code comme celui-ci peut être utilisé.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Une classe DummyBeanBuilderssera générée, qui a une méthode statique dummyBeanUpdaterpour créer des copies superficielles, de la même manière que vous le feriez manuellement.

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();
Lars Bohl
la source
0

Utilisez gsonpour dupliquer un objet.

public static <T>T copyObject(Object object){
    Gson gson = new Gson();
    JsonObject jsonObject = gson.toJsonTree(object).getAsJsonObject();
    return gson.fromJson(jsonObject,(Type) object.getClass());
}

Supposons que j'ai un objet person.

Person copyPerson = copyObject(person);
tuhin47
la source