Trochę trudno jest zaimplementować funkcję głębokiego kopiowania obiektów. Jakie kroki podejmujesz, aby zapewnić, że oryginalny obiekt i sklonowany obiekt nie mają odniesienia?
Trochę trudno jest zaimplementować funkcję głębokiego kopiowania obiektów. Jakie kroki podejmujesz, aby zapewnić, że oryginalny obiekt i sklonowany obiekt nie mają odniesienia?
Odpowiedzi:
Bezpiecznym sposobem jest serializacja obiektu, a następnie deserializacja. To gwarantuje, że wszystko jest zupełnie nowym odniesieniem.
Oto artykuł o tym, jak to zrobić skutecznie.
Ostrzeżenia: Możliwe jest, że klasy zastąpią serializację, tak że nowe instancje nie są tworzone, np. Dla singletonów. Również to oczywiście nie działa, jeśli twoich zajęć nie można szeregować.
Kilka osób wspomniało o używaniu lub zastępowaniu Object.clone()
. Nie rób tego Object.clone()
ma pewne poważne problemy, a jego stosowanie jest w większości przypadków odradzane. Pełna odpowiedź znajduje się w punkcie 11 z „ Effective Java ” Joshua Blocha. Wierzę, że możesz bezpiecznie używać Object.clone()
na tablicach typu pierwotnego, ale oprócz tego musisz rozsądnie traktować prawidłowe używanie i zastępowanie klonu.
Schematy oparte na serializacji (XML lub innej) są kludgy.
Nie ma tutaj łatwej odpowiedzi. Jeśli chcesz głęboko skopiować obiekt, musisz przejść przez wykres obiektu i skopiować każdy obiekt podrzędny jawnie za pomocą konstruktora obiektu lub statycznej metody fabrycznej, która z kolei głęboko kopiuje obiekt podrzędny. Niezmienne (np. String
S) nie muszą być kopiowane. Na marginesie, powinieneś faworyzować niezmienność z tego powodu.
Możesz wykonać głęboką kopię z serializacją bez tworzenia plików.
Twój obiekt, który chcesz skopiować, będzie musiał implement serializable
. Jeśli klasa nie jest ostateczna lub nie można jej zmodyfikować, rozszerz ją i zaimplementuj możliwość serializacji.
Konwertuj klasę na strumień bajtów:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
oos.close();
bos.close();
byte[] byteData = bos.toByteArray();
Przywróć klasę ze strumienia bajtów:
ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
(Object) object = (Object) new ObjectInputStream(bais).readObject();
instance
w tym przypadku niemożliwa do serializacji ?
Możesz wykonać głęboki klon oparty na serializacji za pomocą org.apache.commons.lang3.SerializationUtils.clone(T)
Apache Commons Lang, ale bądź ostrożny - wydajność jest fatalna.
Ogólnie rzecz biorąc, najlepszą praktyką jest pisanie własnych metod klonowania dla każdej klasy obiektu na grafie obiektowym wymagającym klonowania.
org.apache.commons.lang.SerializationUtils
Jednym ze sposobów implementacji głębokiego kopiowania jest dodanie konstruktorów kopiowania do każdej powiązanej klasy. Konstruktor kopiujący przyjmuje instancję „tego” jako swój pojedynczy argument i kopiuje z niej wszystkie wartości. Dość trochę pracy, ale całkiem proste i bezpieczne.
EDYCJA: pamiętaj, że nie musisz używać metod dostępu do odczytu pól. Możesz uzyskać bezpośredni dostęp do wszystkich pól, ponieważ instancja źródłowa jest zawsze tego samego typu co instancja z konstruktorem kopiowania. Oczywiste, ale można je przeoczyć.
Przykład:
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;
}
}
Edycja: Zauważ, że podczas korzystania z konstruktorów kopiowania musisz znać typ środowiska wykonawczego kopiowanego obiektu. Przy powyższym podejściu nie można łatwo skopiować listy mieszanej (możesz to zrobić za pomocą kodu refleksyjnego).
Toyota
, Twój kod umieści Car
na liście docelowej. Właściwe klonowanie wymaga na ogół, aby klasa dostarczyła metodę wirtualnej fabryki, której umowa stanowi, że zwróci nowy obiekt własnej klasy; sam konstruktor kopii powinien protected
zapewnić, że będzie on używany tylko do konstruowania obiektów, których dokładny typ odpowiada typowi kopiowanego obiektu).
Możesz użyć biblioteki, która ma prosty interfejs API i wykonuje stosunkowo szybkie klonowanie z odbiciem (powinno być szybsze niż metody serializacji).
Cloner cloner = new Cloner();
MyClass clone = cloner.deepClone(o);
// clone is a deep-clone of o
Apache commons oferuje szybki sposób głębokiego klonowania obiektu.
My_Object object2= org.apache.commons.lang.SerializationUtils.clone(object1);
XStream jest naprawdę przydatny w takich przypadkach. Oto prosty kod do klonowania
private static final XStream XSTREAM = new XStream();
...
Object newObject = XSTREAM.fromXML(XSTREAM.toXML(obj));
Jednym bardzo łatwym i prostym podejściem jest użycie Jackson JSON do serializacji złożonego obiektu Java do JSON i ponowne odczytanie go.
Dla użytkowników Spring Framework . Korzystanie z klasy org.springframework.util.SerializationUtils
:
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T object) {
return (T) SerializationUtils.deserialize(SerializationUtils.serialize(object));
}
Do skomplikowanych obiektów i gdy wydajność nie jest znacząca, używam biblioteki json, takiej jak gson aby serializować obiekt do tekstu json, a następnie deserializować tekst, aby uzyskać nowy obiekt.
gson, który oparty na odbiciu będzie działał w większości przypadków, z tym wyjątkiem, że transient
pola nie zostaną skopiowane, a obiekty z referencją cykliczną z podaniem przyczyny 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);
}
Użyj XStream ( http://x-stream.github.io/ ). Możesz nawet kontrolować, które właściwości możesz zignorować za pomocą adnotacji lub jawnie podając nazwę właściwości do klasy XStream. Ponadto nie trzeba implementować klonowalnego interfejsu.
Głębokiego kopiowania można dokonać tylko za zgodą każdej klasy. Jeśli masz kontrolę nad hierarchią klas, możesz zaimplementować klonowalny interfejs i zaimplementować metodę klonowania. W przeciwnym razie robienie głębokiej kopii nie jest bezpieczne, ponieważ obiekt może również współdzielić zasoby inne niż dane (np. Połączenia z bazą danych). Ogólnie jednak głębokie kopiowanie jest uważane za złą praktykę w środowisku Java i należy tego unikać poprzez odpowiednie praktyki projektowe.
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 wykonuje naprawdę dobrą robotę, głęboko klonując fasolę.
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);
W tym przypadku klasa MyPerson i MyAddress musi implementować interfejs z możliwością szeregowania
Używanie Jacksona do serializacji i deserializacji obiektu. Ta implementacja nie wymaga, aby obiekt zaimplementował klasę 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);
}