Copia profonda, copia superficiale, clone

Ho bisogno di chiarimenti sulle differenze tra copia profonda, copia superficiale e clone in Java

Sfortunatamente, “copia superficiale”, “copia profonda” e “clone” sono termini piuttosto mal definiti.


Nel contesto Java, dobbiamo prima fare una distinzione tra “copiare un valore” e “copiare un object”.

int a = 1; int b = a; // copying a value int[] s = new int[]{42}; int[] t = s; // copying a value (the object reference for the array above) StringBuffer sb = new StringBuffer("Hi mom"); // copying an object. StringBuffer sb2 = new StringBuffer(sb); 

In breve, un’assegnazione di un riferimento a una variabile il cui tipo è un tipo di riferimento è “copia di un valore” in cui il valore è il riferimento all’object. Per copiare un object, qualcosa deve usare di new , esplicitamente o sotto il cofano.


Ora per la copia “superficiale” e “profonda” di oggetti. La copia superficiale generalmente significa copiare solo un livello di un object, mentre la copia profonda generalmente significa copiare più di un livello. Il problema è nel decidere cosa intendiamo per livello. Considera questo:

 public class Example { public int foo; public int[] bar; public Example() { }; public Example(int foo, int[] bar) { this.foo = foo; this.bar = bar; }; } Example eg1 = new Example(1, new int[]{1, 2}); Example eg2 = ... 

L’interpretazione normale è che una copia “superficiale” di eg1 sarebbe un nuovo object Example cui foo uguale a 1 e il cui campo bar riferisce allo stesso array dell’originale; per esempio

 Example eg2 = new Example(eg1.foo, eg1.bar); 

La normale interpretazione di una copia “profonda” di eg1 sarebbe un nuovo object Example cui foo uguale a 1 e il cui campo bar riferisce a una copia dell’array originale; per esempio

 Example eg2 = new Example(eg1.foo, Arrays.copy(eg1.bar)); 

(Le persone provenienti da uno sfondo C / C ++ potrebbero dire che un’assegnazione di riferimento produce una copia superficiale, ma questo non è ciò che normalmente intendiamo per copiatura superficiale nel contesto Java …)

Esistono altre due domande / aree di incertezza:

  • Quanto è profondo il profondo? Si ferma a due livelli? Tre livelli? Significa l’intero grafico degli oggetti connessi?

  • Che dire dei tipi di dati incapsulati; ad esempio una stringa? Una stringa non è in realtà solo un object. Infatti, è un “object” con alcuni campi scalari e un riferimento a una matrice di caratteri. Tuttavia, l’array di caratteri è completamente nascosto dall’API. Quindi, quando parliamo di copiare una stringa, ha senso chiamarla una copia “superficiale” o una copia “profonda”? O dovremmo chiamarlo solo una copia?


Finalmente, clone. Il clone è un metodo che esiste su tutte le classi (e gli array) che si ritiene generalmente produca una copia dell’object target. Però:

  • La specifica di questo metodo non dice deliberatamente se si tratta di una copia superficiale o profonda (supponendo che sia una distinzione significativa).

  • In effetti, la specifica non specifica in modo specifico che il clone produca un nuovo object.

Ecco cosa dice la javadoc :

“Crea e restituisce una copia di questo object.Il significato preciso di” copia “può dipendere dalla class dell’object.L’intenzione generale è che, per qualsiasi object x, l’espressione x.clone() != x sarà vera e che l’espressione x.clone().getClass() == x.getClass() sarà vera, ma questi non sono requisiti assoluti, mentre in genere è x.clone().equals(x) sii vero, questo non è un requisito assoluto. “

Nota che questo sta dicendo che ad un estremo il clone potrebbe essere l’object bersaglio, e all’estremo opposto il clone potrebbe non essere uguale all’originale. E questo presuppone che il clone sia persino supportato.

In breve, clone potenzialmente significa qualcosa di diverso per ogni class Java.


Alcune persone sostengono (come fa @supercat nei commenti) che il metodo clone() Java è rotto. Ma penso che la conclusione corretta sia che il concetto di clone è rotto nel contesto di OO. AFAIK, è imansible sviluppare un modello unificato di clonazione che sia coerente e utilizzabile in tutti i tipi di object.

Il termine “clone” è ambiguo (anche se la libreria di classi Java include un’interfaccia Cloneable ) e può fare riferimento a una copia profonda oa una copia superficiale. Le copie profonde / superficiali non sono specificamente legate a Java, ma sono un concetto generale relativo alla creazione di una copia di un object e si riferiscono a come vengono copiati anche i membri di un object.

Ad esempio, supponiamo di avere una class di persone:

 class Person { String name; List emailAddresses } 

Come cloni oggetti di questa class? Se stai eseguendo una copia superficiale, potresti copiare il nome e inserire un riferimento a emailAddresses nel nuovo object. Ma se hai modificato il contenuto dell’elenco emailAddresses , dovresti modificare l’elenco in entrambe le copie (poiché è così che funzionano i riferimenti agli oggetti).

Una copia approfondita significherebbe che copi in modo ricorsivo ogni membro, quindi dovresti creare una nuova List per la nuova Person , quindi copiare i contenuti dal vecchio al nuovo object.

Sebbene l’esempio sopra sia banale, le differenze tra le copie profonde e superficiali sono significative e hanno un impatto importante su qualsiasi applicazione, specialmente se si sta tentando di escogitare un metodo di clonazione generico in anticipo, senza sapere come qualcuno potrebbe usarlo in seguito. Ci sono momentjs in cui hai bisogno di semantica profonda o superficiale, o di un ibrido in cui copri in profondità alcuni membri ma non altri.

  • Copia profonda: clona questo object e ogni riferimento a tutti gli altri oggetti che ha
  • Copia superficiale: clona questo object e conserva i suoi riferimenti
  • Object clone () genera CloneNotSupportedException: Non è specificato se questo dovrebbe restituire una copia profonda o superficiale, ma almeno: o.clone ()! = O

I termini “copia superficiale” e “copia profonda” sono un po ‘vaghi; Suggerirei di usare i termini “membro clone” e quello che chiamerei un “clone semantico”. Un “clone membro” di un object è un nuovo object, dello stesso tipo di runtime dell’originale, per ogni campo, il sistema esegue effettivamente “newObject.field = oldObject.field”. La base Object.Clone () esegue un clone memberwise; la clonazione membro è generalmente il giusto punto di partenza per la clonazione di un object, ma nella maggior parte dei casi sarà necessario un “lavoro di correzione” dopo un clone membro. In molti casi il tentativo di usare un object prodotto tramite clone di memberwise senza prima eseguire la correzione necessaria causerà cose brutte, incluso il danneggiamento dell’object che è stato clonato e probabilmente anche altri oggetti. Alcune persone usano il termine “clonazione superficiale” per riferirsi alla clonazione membro, ma non è l’unico uso del termine.

Un “clone semantico” è un object che contiene gli stessi dati dell’originale, dal punto di vista del tipo . Per esaminare, prendi in considerazione una BigList che contiene una matrice> e un conteggio. Un clone di tale object a livello semantico eseguirà un clone membro, quindi sostituirà l’array> con un nuovo array, creerà nuovi array nidificati e copierà tutte le T dalle matrici originali a quelle nuove. Non tenterebbe alcun tipo di deep-cloning delle T stesse . Ironia della sorte, alcune persone si riferiscono alla clonazione di “clonazione superficiale”, mentre altri la chiamano “deep cloning”. Terminologia non esattamente utile.

Mentre ci sono casi in cui la clonazione veramente profonda (ricorsiva ricorsiva di tutti i tipi mutabili) è utile, dovrebbe essere eseguita solo da tipi i cui componenti sono progettati per tale architettura. In molti casi, la clonazione veramente profonda è eccessiva e può interferire con situazioni in cui ciò di cui si ha bisogno è in effetti un object i cui contenuti visibili si riferiscono agli stessi oggetti di un altro (cioè una copia a livello semantico). Nei casi in cui i contenuti visibili di un object derivano ricorsivamente da altri oggetti, un clone a livello semantico implicherebbe un clone profondo ricorsivo, ma nei casi in cui il contenuto visibile è solo un tipo generico, il codice non dovrebbe clonare in profondità alla cieca ogni cosa sembra che potrebbe essere in grado di clonare in profondità.