Come copio un oggetto in Java?


794

Considera il codice seguente:

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'

Quindi, voglio copiare il dumper dumtwoe il cambiamento dumsenza alterare il dumtwo. Ma il codice sopra non lo sta facendo. Quando cambio qualcosa dum, succede dumtwoanche lo stesso cambiamento .

Immagino, quando dico dumtwo = dum, Java copia solo il riferimento . Quindi, c'è un modo per creare una nuova copia dume assegnarla a dumtwo?

Risposte:


611

Crea un costruttore di copie:

class DummyBean {
  private String dummy;

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

Ogni oggetto ha anche un metodo clone che può essere usato per copiare l'oggetto, ma non usarlo. È troppo facile creare una classe e fare un metodo di clonazione improprio. Se hai intenzione di farlo, leggi almeno quello che Joshua Bloch ha da dire al riguardo in Effective Java .


45
Ma poi dovrebbe cambiare il suo codice in DummyBean due = nuovo DummyBean (uno); Giusto?
Chris K,

12
Questo metodo realizza effettivamente la stessa cosa di una copia profonda?
Matthew Piziak,

124
@MatthewPiziak, per me - questo non sarebbe un clone profondo poiché qualsiasi oggetto nidificato farebbe comunque riferimento all'istanza di origine originale, non un duplicato a meno che ogni oggetto di riferimento (tipo non di valore) fornisca lo stesso modello di costruzione come sopra.
SliverNinja - MSFT,

17
@Timmmm: Sì, faranno riferimento alla stessa stringa ma poiché è immutabile, è ok. Lo stesso vale per i primitivi. Per i non primitivi, dovresti semplicemente copiare ricorsivamente la chiamata del contructor. ad es. se DummyBean fa riferimento a FooBar, allora FooBar dovrebbe avere il contraccolpo FooBar (FooBar un altro) e il manichino dovrebbe chiamare this.foobar = new FooBar (another.foobar)
egaga,

7
@ChristianVielma: No, non sarà "johndoe". Come ha detto Timmmm, la stringa stessa è immutabile. Con uno, setDummy (..) imposti il ​​riferimento in uno per puntare a "johndoe", ma non quello in uno.
keuleJ,

404

Base: Copia di oggetti in Java.

Supponiamo che un oggetto obj1contenga due oggetti, contenuteObj1 e contenuteObj2 .
inserisci qui la descrizione dell'immagine

copia superficiale: la copia
superficiale crea una nuova instancedella stessa classe e copia tutti i campi nella nuova istanza e la restituisce. La classe di oggetti fornisce un clonemetodo e fornisce supporto per la copia superficiale.
inserisci qui la descrizione dell'immagine

Copia profonda:
una copia profonda si verifica quando un oggetto viene copiato insieme agli oggetti a cui si riferisce . L'immagine sotto mostra obj1dopo che è stata eseguita una copia profonda su di essa. Non solo è obj1stato copiato , ma anche gli oggetti in esso contenuti sono stati copiati. Possiamo usare Java Object Serializationper fare una copia profonda. Sfortunatamente, anche questo approccio presenta alcuni problemi ( esempi dettagliati ).
inserisci qui la descrizione dell'immagine

Possibili problemi:
clone è difficile da implementare correttamente.
È meglio usare la copia difensiva , i costruttori di copie (come risposta @egaga) o i metodi di fabbrica statici .

  1. Se hai un oggetto, sai che ha un clone()metodo pubblico , ma non conosci il tipo di oggetto al momento della compilazione, allora hai un problema. Java ha un'interfaccia chiamata Cloneable. In pratica, dovremmo implementare questa interfaccia se vogliamo creare un oggetto Cloneable. Object.cloneè protetto , quindi dobbiamo sostituirlo con un metodo pubblico affinché sia ​​accessibile.
  2. Un altro problema sorge quando proviamo a copiare in profondità un oggetto complesso . Supponiamo che anche il clone()metodo di tutte le variabili oggetto membro esegua una copia approfondita, questo è troppo rischioso di un'ipotesi. È necessario controllare il codice in tutte le classi.

Ad esempio org.apache.commons.lang.SerializationUtils avrà un metodo per il clone Deep usando la serializzazione ( Source ). Se abbiamo bisogno di clonare Bean, ci sono un paio di metodi di utilità in org.apache.commons.beanutils ( Fonte ).

  • cloneBean Clonerà un bean in base ai getter e ai setter di proprietà disponibili, anche se la classe bean stessa non implementa Cloneable.
  • copyProperties copierà i valori delle proprietà dal bean di origine nel bean di destinazione per tutti i casi in cui i nomi delle proprietà sono uguali.

1
Puoi per favore spiegare che cosa è l'oggetto contenuto in un altro?
Freakyuser,

1
@Chandra Sekhar "La copia superficiale crea una nuova istanza della stessa classe e copia tutti i campi nella nuova istanza e la restituisce" è sbagliato menzionare tutti i campi, gli oggetti bcz non vengono copiati solo i riferimenti vengono copiati che puntano a lo stesso oggetto a cui puntava quello vecchio (originale).
JAVA,

4
@sunny - La descrizione di Chandra è corretta. E così è la tua descrizione di ciò che accade; Sto dicendo che hai una comprensione errata del significato di "copia tutti i campi". Il campo è il riferimento, non è l'oggetto a cui si fa riferimento. "copiare tutti i campi" significa "copiare tutti quei riferimenti". È bene che tu abbia sottolineato cosa significa esattamente, per chiunque abbia la tua stessa interpretazione errata, della frase "copiare tutti i campi". :)
ToolmakerSteve

2
... se pensiamo in termini di un linguaggio OO di livello inferiore, con "puntatori" agli oggetti, tale campo conterrebbe l'indirizzo in memoria (come "0x70FF1234") in cui si trovano i dati dell'oggetto. Tale indirizzo è il "valore del campo" che viene copiato (assegnato). Hai ragione sul fatto che il risultato finale è che entrambi gli oggetti hanno campi che fanno riferimento (punto a) allo stesso oggetto.
ToolmakerSteve

127

Nel pacchetto import org.apache.commons.lang.SerializationUtils;c'è un metodo:

SerializationUtils.clone(Object);

Esempio:

this.myObjectCloned = SerializationUtils.clone(this.object);

59
Fino a quando l'oggetto viene implementatoSerializable
Androiderson,

2
In questo caso l'oggetto clonato non ha alcun riferimento all'originale, se l'ultimo è statico.
Dante,

8
Una libreria di terze parti solo per clonare oggetti!
Khan,

2
@Khan, "una libreria di terze parti solo per" è una discussione completamente separata! : D
Charles Wood,

103

Segui come di seguito:

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

e ovunque tu voglia ottenere un altro oggetto, esegui semplicemente la clonazione. per esempio:

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

1
Hai provato questo? Potrei usarlo per il mio progetto ed è importante essere corretti.
nebbioso

2
@misty l'ho provato. Funziona perfettamente sulla mia app di produzione
Andrii Kovalchuk,

Dopo la clonazione, quando si modifica l'oggetto originale, si modifica anche il clone.
Sibish,

4
Questo è sbagliato in quanto non è stata richiesta una copia approfondita.
Bluehorn,

1
Questo metodo clona il puntatore che punta all'oggetto clonabile, ma tutte le proprietà all'interno di entrambi gli oggetti sono uguali, quindi c'è un nuovo oggetto creato nella memoria, ma i dati all'interno di ogni oggetto sono gli stessi dati della memoria
Omar HossamEldin

40

Perché non esiste una risposta per l'utilizzo dell'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;
        }
    }

È davvero semplice

EDIT: include l'oggetto figlio tramite ricorsione

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

Sembra molto meglio, ma devi solo considerare i campi finali in quanto setAccessible (true) potrebbe non riuscire, quindi potresti dover gestire separatamente l'eccezione IllegalAccessException generata quando si chiama field.set (clone, field.get (obj)) separatamente.
Max

1
Mi è piaciuto così tanto, ma puoi rifattarlo per usare i generici? private static <T> T cloneObject (T obj) {....}
Adelin

2
Penso che sia un problema quando abbiamo un riferimento dalle proprietà ai genitori: Class A { B child; } Class B{ A parent; }
nhthai,

Fallisce anche in questa situazione, deve essere gestita, ci giocherò domani. class car { car car = new car(); }
Ján Яabčan,

2
Questo è soggetto a errori. Non sono sicuro di come gestirà le raccolte
ACV,

31

Uso la libreria JSON di Google per serializzarla, quindi creo una nuova istanza dell'oggetto serializzato. Fa copia profonda con alcune restrizioni:

  • non ci possono essere riferimenti ricorsivi

  • non copia array di tipi diversi

  • matrici ed elenchi devono essere digitati o non troveranno la classe da istanziare

  • potresti dover incapsulare le stringhe in una classe che ti dichiari

Uso anche questa classe per salvare le preferenze dell'utente, Windows e quant'altro da ricaricare in fase di esecuzione. È molto facile da usare ed 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;
}
}

Funziona benissimo. Ma fai attenzione se provi a clonare qualcosa come List <Integer>. Sarà difettoso, i miei numeri interi sono stati trasformati in doppio, 100.0. Mi ci è voluto molto tempo per capire perché sono così. La soluzione era clonarli interi uno per uno e aggiungerli all'elenco in un ciclo.
Paakjis,


14

Aggiungi Cloneablee sotto il codice alla tua classe

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

Usa questo clonedObject = (YourClass) yourClassObject.clone();



12

Anche questo funziona. Supponendo modello

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

Per prima cosa aggiungi compile 'com.google.code.gson:gson:2.8.1'alla tua app> gradle & sync. Poi

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

Puoi escludere usando un campo usando la transientparola chiave dopo il modificatore di accesso.

Nota: questa è una cattiva pratica. Inoltre, non è consigliabile utilizzare Cloneableo JavaSerializationè lento e rotto. Costruttore di copia di scrittura per le migliori prestazioni rif .

Qualcosa di simile a

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

Statistiche di prova di 90000 iterazione: la
linea UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);impiega 808 ms

La linea UserAccount clone = new UserAccount(aO);richiede meno di 1 ms

Conclusione: usa gson se il tuo capo è pazzo e preferisci la velocità. Usa il costruttore della seconda copia se preferisci la qualità.

Puoi anche utilizzare il plug- in del generatore di codice del costruttore della copia in Android Studio.


Perché l'hai suggerito se è una cattiva pratica?
Parth Mehrotra,

Grazie @ParthMehrotra ora migliorato
Qamar


9

Utilizzare un'utilità di clonazione profonda:

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

Questo copia in profondità qualsiasi oggetto java, dai un'occhiata a https://github.com/kostaskougios/cloning


1
non ha funzionato per me usando una classe personalizzata. ottenendo la seguente eccezione: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker

9

Deep Cloning è la tua risposta, che richiede l'implementazione Cloneabledell'interfaccia e l'override del clone()metodo.

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

Lo chiamerai così DummyBean dumtwo = dum.clone();


2
dummy, a String, è immutabile, non è necessario copiarlo
Steve Kuo,

7

Per fare ciò devi clonare l'oggetto in qualche modo. Sebbene Java abbia un meccanismo di clonazione, non utilizzarlo se non è necessario. Crea un metodo di copia che funzioni per te, quindi esegui:

dumtwo = dum.copy();

Ecco alcuni consigli su diverse tecniche per realizzare una copia.


6

Oltre alla copia esplicita, un altro approccio è quello di rendere l'oggetto immutabile (nessun seto altri metodi mutatori). In questo modo la domanda non si pone mai. L'immutabilità diventa più difficile con oggetti più grandi, ma l'altro lato è che ti spinge nella direzione della scissione in piccoli oggetti e compositi coerenti.


5

Alternativa al metodo di copia del costruttore egaga . Probabilmente hai già un POJO, quindi aggiungi un altro metodo copy()che restituisce una copia dell'oggetto inizializzato.

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
}

Se hai già un DummyBeane vuoi una copia:

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());

Produzione:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Ma entrambi funzionano bene, dipende da te ...


3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}

3

Puoi eseguire il deep copy automaticamente con XStream, da http://x-stream.github.io/ :

XStream è una semplice libreria per serializzare oggetti in XML e viceversa.

Aggiungilo al tuo progetto (se usi maven)

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

Poi

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

Con questo hai una copia senza la necessità di implementare alcuna interfaccia di clonazione.


29
La conversione in / da XML non sembra molto ... elegante. Per dirla in parole povere!
Timmmm,

Dai un'occhiata a java.beans.XMLEncoderun'API Java standard che serializzi anche in XML (anche se non proprio per scopi di copia approfondita).
Jaime Hablutzel,

1
ti rendi conto di quanto sia pesante?
mahieddine,

1
Secondo me un modo eccessivo per l'overhead, dal momento che è necessario aggiungere una libreria di terze parti ed eseguire la serializzazione degli oggetti che molto probabilmente ha un enorme impatto sulle prestazioni.
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();
   }

  }
}

e nel tuo codice:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();

2
Non ha senso nel set "genera CloneNotSupportedException" nella dichiarazione se si tenta di rilevare l'eccezione e non viene generata. Quindi, puoi semplicemente rimuoverlo.
Christian,

2

Passa l'oggetto che desideri copiare e ottieni l'oggetto che desideri:

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;

    }

Ora analizza l' objDestoggetto desiderato.

Buona programmazione!


1

Puoi provare a implementare Cloneablee utilizzare il clone()metodo; tuttavia, se usi il metodo clone, dovresti - per impostazione predefinita - sostituire SEMPRE Objectil public Object clone()metodo.


1

Se è possibile aggiungere un'annotazione al file di origine, è possibile utilizzare un processore di annotazione o un generatore di codice come questo .

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

DummyBeanBuildersVerrà generata una classe , che ha un metodo statico dummyBeanUpdaterper creare copie superficiali, allo stesso modo in cui lo faresti manualmente.

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();

0

Utilizzare gsonper duplicare un oggetto.

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

Si supponga che ho un oggetto person.Così

Person copyPerson = copyObject(person);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.