Java: soluzione consigliata per la clonazione profonda / la copia di un'istanza


177

Mi chiedo se esiste un modo consigliato per eseguire il clone / copia di istanza in profondità in Java.

Ho in mente 3 soluzioni, ma potrei essermene perso alcune e vorrei avere la tua opinione

modifica: includi la proposta di Bohzo e affina la domanda: si tratta più della clonazione profonda che della clonazione superficiale.

Fallo da solo:

codificare manualmente le proprietà del clone dopo le proprietà e verificare che anche le istanze mutabili vengano clonate.
pro:
- controllo di ciò che verrà eseguito
-
contro esecuzione rapida :
- noioso da scrivere e mantenere
- soggetto a bug (errore copia / incolla, proprietà mancante, proprietà mutabile riassegnata)

Usa la riflessione:

Con i tuoi strumenti di riflessione o con un helper esterno (come i jakarta common-bean) è facile scrivere un metodo di copia generico che eseguirà il lavoro su una riga.
pro:
- facile da scrivere
- nessun
contro di manutenzione :
- minor controllo di ciò che accade
- bug soggetto a oggetto mutabile se lo strumento di riflessione non clona troppo gli oggetti secondari
- esecuzione più lenta

Usa framework clone:

Usa un framework che lo faccia per te, come:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

pro:
- lo stesso della riflessione
- maggiore controllo su ciò che sarà esattamente clonato.
contro:
- ogni istanza mutabile è completamente clonata, anche alla fine della gerarchia
- potrebbe essere molto lenta da eseguire

Utilizzare la strumentazione bytecode per scrivere clone in fase di esecuzione

javassit , BCEL o cglib potrebbero essere utilizzati per generare un cloner dedicato con la stessa velocità con cui è stata scritta una mano. Qualcuno conosce una lib usando uno di questi strumenti per questo scopo?

Cosa mi sono perso qui?
Quale consiglieresti?

Grazie.


1
apparentemente Java Deep Cloning Library si è spostato qui: code.google.com/p/cloning
Mr_and_Mrs_D

Risposte:


155

Per la clonazione profonda (clona l'intera gerarchia di oggetti):

  • commons-lang SerializationUtils - utilizzando la serializzazione - se tutte le classi sono sotto il tuo controllo e puoi forzare l'implementazione Serializable.

  • Java Deep Cloning Library - using reflection - nei casi in cui le classi o gli oggetti che si desidera clonare siano fuori dal proprio controllo (una libreria di terze parti) e non è possibile implementarli Serializable, o nei casi in cui non si desidera implementare Serializable.

Per la clonazione superficiale (clona solo le proprietà di primo livello):

Ho deliberatamente omesso l'opzione "fai-da-te": le API di cui sopra forniscono un buon controllo su cosa fare e cosa non clonare (ad esempio usando transiento String[] ignoreProperties), quindi non è preferibile reinventare la ruota.


Grazie Bozho, è prezioso. E sono d'accordo con te sull'opzione fai-da-te! Hai mai provato la serializzazione dei beni comuni e / o la lib della clonazione profonda? E i perf?
Guillaume,

sì, ho usato tutte le opzioni sopra, per i motivi sopra riportati :) solo la libreria di clonazione aveva alcuni problemi quando erano coinvolti i proxy CGLIB e mancava alcune funzionalità desiderate, ma penso che ora dovrebbero essere risolti.
Bozho,

Ehi, se la mia Entità è collegata e ho cose pigre, SerializationUtils controlla il database per le proprietà pigre? Perché questo è quello che voglio, e non lo fa!
Cosmin Cosmin,

se hai una sessione attiva, sì.
Bozho,

@Bozho Quindi vuoi dire se tutti gli oggetti all'interno del bean stanno implementando serializzabile, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) eseguirà una copia approfondita?
hop

36

Il libro di Joshua Bloch ha un intero capitolo intitolato "Articolo 10: Sovrascrivi il clone con giudizio" in cui spiega perché ignorare il clone per la maggior parte è una cattiva idea perché le specifiche Java per esso creano molti problemi.

Fornisce alcune alternative:

  • Utilizzare un modello di fabbrica al posto di un costruttore:

         public static Yum newInstance(Yum yum);
  • Usa un costruttore di copie:

         public Yum(Yum yum);

Tutte le classi di raccolta in Java supportano il costruttore di copie (ad esempio new ArrayList (l);)


1
Concordato. Nel mio progetto ho definito Copyableun'interfaccia che contiene un getCopy()metodo. Basta usare il modello prototipo manualmente.
gpampara,

Beh, non stavo chiedendo dell'interfaccia clonabile, ma come eseguire un'operazione di clonazione / copia profonda. Con un costruttore o una fabbrica è ancora necessario creare la nuova istanza dalla fonte.
Guillaume,

@Guillaume Penso che devi stare attento nell'usare le parole clone / copia profonda. Clonare e copiare in java NON significa la stessa cosa. Le specifiche Java hanno molto da dire su questo .... Penso che tu voglia una copia profonda da quello che posso dire.
LeWoody,

OK Le specifiche Java sono precise su cosa sia un clone ... Ma possiamo anche parlare del clone in un significato più comune ... Ad esempio, una delle librerie consigliate da Bohzo si chiama "Java Deep Cloning Library" ...
Guillaume,

2
@LWoodyiii questo newInstance()metodo e il Yumcostruttore farebbero copia profonda o copia superficiale?
Geek,


5

Utilizzare XStream toXML / fromXML in memoria. Estremamente veloce ed è in circolazione da molto tempo e sta andando forte. Gli oggetti non devono essere serializzabili e non devi usare la riflessione (anche se XStream lo fa). XStream può discernere le variabili che puntano allo stesso oggetto e non fare accidentalmente due copie complete dell'istanza. Molti dettagli del genere sono stati messi a punto nel corso degli anni. L'ho usato per un certo numero di anni ed è un tentativo. È facile da usare come puoi immaginare.

new XStream().toXML(myObj)

o

new XStream().fromXML(myXML)

Per clonare,

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

Più succintamente:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));

3

Per oggetti complicati e quando le prestazioni non sono significative, uso 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 <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);

}

2

Dipende.

Per la velocità, usa fai-da-te. A prova di proiettile, utilizzare la riflessione.

A proposito, la serializzazione non è la stessa di refl, poiché alcuni oggetti possono fornire metodi di serializzazione sovrascritti (readObject / writeObject) e possono essere buggy


1
la riflessione non è a prova di proiettile: può portare in alcune situazioni in cui l'oggetto clonato fa riferimento alla tua fonte ... Se la fonte cambia, anche il clone cambierà!
Guillaume,

1

Consiglierei il modo DIY che, combinato con un buon metodo hashCode () e equals (), dovrebbe essere facile da provare in un unit test.


beh, il pigro mi presta molto quando crea un codice così fittizio. Ma sembra il percorso più saggio ...
Guillaume,

2
scusa, ma il fai-da-te è la strada da percorrere SOLO se nessun'altra soluzione è adatta a te..che non è quasi mai
Bozho,

1

Suggerirei di sovrascrivere Object.clone (), chiamare prima super.clone () e poi chiamare ref = ref.clone () su tutti i riferimenti che si desidera vengano copiati in profondità. È più o meno l' approccio Fai da te ma ha bisogno di un po 'meno di codifica.


2
Questo è uno dei tanti problemi del metodo clone (rotto): in una gerarchia di classi devi sempre chiamare super.clone (), che può essere facilmente dimenticato, ecco perché preferirei usare un costruttore di copie.
helpermethod,

0

Per la clonazione profonda implementare Serializable su ogni classe che si desidera clonare in questo modo

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

E quindi utilizzare questa funzione:

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

come questo: Obj newObject = (Obj)deepClone(oldObject);

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.