È 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?
È 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?
Risposte:
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.
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. String
Non è necessario copiare gli immutabili (ad es .). A parte questo, dovresti favorire l'immutabilità per questo motivo.
È 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();
instance
in questo caso?
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.
org.apache.commons.lang.SerializationUtils
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).
Toyota
, il codice inserirà un Car
nell'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 protected
garantire che verrà utilizzato solo per costruire oggetti il cui tipo preciso corrisponda a quello dell'oggetto da copiare).
È 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
Apache Commons offre un modo rapido per clonare in profondità un oggetto.
My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);
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));
Un approccio molto semplice e semplice è quello di utilizzare Jackson JSON per serializzare Java Object complessi su JSON e rileggerlo.
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));
}
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 transient
campi 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);
}
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.
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.
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));
}
}
BeanUtils fa davvero un ottimo lavoro nella clonazione di fagioli.
BeanUtils.cloneBean(obj);
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
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);
}