Come si fa una copia profonda di un oggetto?


301

È un po 'difficile implementare una funzione di copia di oggetti profondi. Quali passi fai per garantire che l'oggetto originale e quello clonato non condividano alcun riferimento?


4
Kryo ha il supporto integrato per la copia / clonazione . Si tratta della copia diretta da oggetto a oggetto, non oggetto-> byte-> oggetto.
NateS,

1
Ecco una domanda correlata che è stata posta in seguito: Raccomandazione di utilità di clonazione profonda
Brad Cupit

L'uso della libreria di clonazione mi ha salvato la giornata! github.com/kostaskougios/cloning
gaurav,

Risposte:


168

Un modo sicuro è serializzare l'oggetto, quindi deserializzare. Questo assicura che tutto sia un riferimento nuovo di zecca.

Ecco un articolo su come farlo in modo efficiente.

Avvertenze: è possibile che le classi ignorino la serializzazione in modo tale che non vengano create nuove istanze , ad esempio per i singoli. Anche questo ovviamente non funziona se le tue lezioni non sono serializzabili.


6
Tenere presente che l'implementazione FastByteArrayOutputStream fornita nell'articolo potrebbe essere più efficiente. Utilizza un'espansione in stile ArrayList quando il buffer si riempie, ma è meglio usare un approccio di espansione in stile LinkedList. Invece di creare un nuovo buffer 2x e memorizzare il buffer corrente, mantenere un elenco collegato di buffer, aggiungendone uno nuovo quando la corrente si riempie. Se si riceve una richiesta per scrivere più dati di quelli che rientrerebbero nella dimensione del buffer predefinita, creare un nodo buffer esattamente grande quanto la richiesta; i nodi non devono avere le stesse dimensioni.
Brian Harris,


Un buon articolo, che spiega la copia approfondita attraverso la serializzazione: javaworld.com/article/2077578/learn-java/…
Ad Infinitum,

L'elenco collegato di @BrianHarris non è più efficiente dell'array dinamico. L'inserimento di elementi in un array dinamico è una complessità costante ammortizzata, mentre l'inserimento in un elenco collegato è una complessità lineare
Norill Tempest,

Quanto serializzare e deserializzare più lentamente dell'approccio del costruttore di copie?
Woland,

75

Alcune persone hanno menzionato l'utilizzo o l'override Object.clone(). Non farlo Object.clone()ha alcuni problemi importanti e il suo uso è scoraggiato nella maggior parte dei casi. Si prega di vedere l'articolo 11, da " Effective Java " di Joshua Bloch per una risposta completa. Credo che tu possa tranquillamente utilizzare Object.clone()array di tipo primitivo, ma a parte questo devi essere giudizioso sull'uso e l'override corretto del clone.

Gli schemi che si basano sulla serializzazione (XML o altro) sono kludgy.

Non c'è una risposta facile qui. Se si desidera copiare in profondità un oggetto, è necessario attraversare il grafico dell'oggetto e copiare esplicitamente ogni oggetto figlio tramite il costruttore della copia dell'oggetto o un metodo statico di fabbrica che a sua volta copia in profondità l'oggetto figlio. StringNon è necessario copiare gli immutabili (ad es .). A parte questo, dovresti favorire l'immutabilità per questo motivo.


58

È possibile effettuare una copia approfondita con la serializzazione senza creare file.

Il tuo oggetto che desideri copiare in profondità dovrà farlo implement serializable. Se la classe non è definitiva o non può essere modificata, estendere la classe e implementare serializzabile.

Converti la tua classe in un flusso di byte:

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

Ripristina la tua classe da un flusso di byte:

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

4
Se la lezione è definitiva, come la estenderesti?
Kumar Manish,

1
La classe MyContainer di @KumarManish implementa Serializable {istanza MyFinalClass; ...}
Matteo T.

Trovo che sia un'ottima risposta. Clone is a past
blackbird014

@MatteoT. come verrà serializzata la proprietà di classe non serializzabile, non serializzabile instancein questo caso?
Farid

40

Puoi fare un clone profondo basato sulla serializzazione usando org.apache.commons.lang3.SerializationUtils.clone(T)in Apache Commons Lang, ma fai attenzione: le prestazioni sono spaventose.

In generale, è consigliabile scrivere i propri metodi di clonazione per ogni classe di un oggetto nel grafico dell'oggetto che richiede la clonazione.


È disponibile anche inorg.apache.commons.lang.SerializationUtils
Pino,

25

Un modo per implementare la copia profonda è aggiungere costruttori di copie a ciascuna classe associata. Un costruttore di copie prende un'istanza di 'this' come unico argomento e ne copia tutti i valori. Abbastanza lavoro, ma piuttosto semplice e sicuro.

EDIT: nota che non è necessario utilizzare metodi di accesso per leggere i campi. È possibile accedere direttamente a tutti i campi perché l'istanza di origine è sempre dello stesso tipo dell'istanza con il costruttore della copia. Ovvio ma potrebbe essere trascurato.

Esempio:

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

Modifica: si noti che quando si utilizzano i costruttori di copie è necessario conoscere il tipo di runtime dell'oggetto che si sta copiando. Con l'approccio sopra non è possibile copiare facilmente un elenco misto (potresti essere in grado di farlo con un codice di riflessione).


1
Sono solo interessato al caso in cui ciò che stai copiando sia una sottoclasse, ma a cui fa riferimento il genitore. È possibile sovrascrivere il costruttore di copie?
Pork 'n' Bunny,

Perché la tua classe genitore si riferisce alla sua sottoclasse? Puoi fare un esempio?
Adriaan Koster,

1
auto di classe pubblica estende il veicolo e quindi si riferisce all'auto come un veicolo. originaList = new ArrayList <Vehicle>; copyList = new ArrayList <Vehicle>; originalList.add (new Car ()); per (Veicolo veicolo: elenco veicoli) {copyList.add (nuovo veicolo (veicolo)); }
Pork 'n' Bunny il

@AdriaanKoster: se l'elenco originale contiene un Toyota, il codice inserirà un Carnell'elenco di destinazione. Una corretta clonazione richiede generalmente che la classe fornisca un metodo factory virtuale il cui contratto stabilisce che restituirà un nuovo oggetto della propria classe; lo stesso contratore di copia dovrebbe protectedgarantire che verrà utilizzato solo per costruire oggetti il ​​cui tipo preciso corrisponda a quello dell'oggetto da copiare).
supercat,

Quindi, se capisco correttamente il tuo suggerimento, il metodo factory chiamerebbe il costruttore di copie private? In che modo il costruttore di copie di una sottoclasse dovrebbe assicurarsi che i campi della superclasse siano inizializzati? Puoi fare un esempio?
Adriaan Koster,

20

È possibile utilizzare una libreria con un'API semplice ed eseguire una clonazione relativamente veloce con reflection (dovrebbe essere più veloce dei metodi di serializzazione).

Cloner cloner = new Cloner();

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

19

Apache Commons offre un modo rapido per clonare in profondità un oggetto.

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

1
Questo funziona solo per l'oggetto che implementa Serializable e anche per tutti i campi in esso implementa Serializable però.
wonhee,

11

XStream è davvero utile in questi casi. Ecco un semplice codice per eseguire la clonazione

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

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

1
Nooo, non è necessario il sovraccarico di xml -ing dell'oggetto.
egelev,

@egeleve Ti rendi conto che stai rispondendo a un commento del '08 giusto? Non uso più Java e probabilmente ora ci sono strumenti migliori. Tuttavia, a quel tempo, serializzare in un formato diverso e quindi serializzare indietro sembrava un buon trucco: era decisamente inefficiente.
sankara,


10

Per gli utenti di Spring Framework . Utilizzando la classe org.springframework.util.SerializationUtils:

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

9

Per oggetti complicati e quando le prestazioni non sono significative uso una libreria json, come gson per serializzare l'oggetto su json text, quindi deserializzare il testo per ottenere un nuovo oggetto.

gson che in base alla riflessione funzionerà nella maggior parte dei casi, tranne per il fatto che i transientcampi non verranno copiati e gli oggetti con riferimento circolare con causa 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);
}

3
Si prega di aderire alle convenzioni di denominazione Java per il proprio e il nostro bene.
Patrick Bergner,

8

Utilizzare XStream ( http://x-stream.github.io/ ). Puoi persino controllare quali proprietà puoi ignorare attraverso le annotazioni o specificando esplicitamente il nome della proprietà nella classe XStream. Inoltre non è necessario implementare l'interfaccia clonabile.


7

La copia in profondità può essere eseguita solo con il consenso di ciascuna classe. Se hai il controllo sulla gerarchia di classi, puoi implementare l'interfaccia clonabile e implementare il metodo Clone. Altrimenti è impossibile eseguire una copia approfondita in modo sicuro poiché l'oggetto potrebbe anche condividere risorse non di dati (ad esempio connessioni al database). In generale, tuttavia, la copia in profondità è considerata una cattiva pratica nell'ambiente Java e deve essere evitata tramite le pratiche di progettazione appropriate.


2
Potresti descrivere le "pratiche di progettazione appropriate"?
fklappan,

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

5

Ho usato Dozer per clonare oggetti Java ed è fantastico, la libreria Kryo è un'altra ottima alternativa.



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

Qui la tua classe MyPerson e MyAddress devono implementare un'interfaccia serilazable


2

Utilizzo di Jackson per serializzare e deserializzare l'oggetto. Questa implementazione non richiede all'oggetto di implementare la classe Serializable.

  <T> T clone(T object, Class<T> clazzType) throws IOException {

    final ObjectMapper objMapper = new ObjectMapper();
    String jsonStr= objMapper.writeValueAsString(object);

    return objMapper.readValue(jsonStr, clazzType);

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