Jak wykonać głęboką kopię obiektu?


301

Trochę trudno jest zaimplementować funkcję głębokiego kopiowania obiektów. Jakie kroki podejmujesz, aby zapewnić, że oryginalny obiekt i sklonowany obiekt nie mają odniesienia?


4
Kryo ma wbudowaną obsługę kopiowania / klonowania . Jest to bezpośrednie kopiowanie z obiektu na obiekt, a nie obiekt-> bajty-> obiekt.
NateS

1
Oto powiązane pytanie, które zostało zadane później:
Polecenie dotyczące

Korzystanie z biblioteki klonowania uratowało mi dzień! github.com/kostaskougios/cloning
gaurav

Odpowiedzi:


168

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


6
Należy pamiętać, że implementacja FastByteArrayOutputStream podana w tym artykule może być bardziej wydajna. Korzysta z rozszerzenia w stylu ArrayList, gdy bufor się zapełnia, ale lepiej jest zastosować metodę rozszerzenia w stylu LinkedList. Zamiast tworzyć nowy bufor 2x i zapamiętywać bieżący bufor, utrzymuj połączoną listę buforów, dodając nowy, gdy bieżący się zapełni. Jeśli pojawi się prośba o zapisanie większej ilości danych niż mieści się w domyślnym rozmiarze bufora, utwórz węzeł bufora, który jest dokładnie tak duży jak żądanie; węzły nie muszą być tego samego rozmiaru.
Brian Harris,


Dobry artykuł, który wyjaśnia głęboki tekst
Ad Infinitum

@BrianHarris połączona lista nie jest bardziej wydajna niż tablica dynamiczna. Wstawianie elementów do tablicy dynamicznej jest amortyzowane stałą złożonością, podczas gdy wstawianie do połączonej listy jest złożonością liniową
Norill Tempest,

Ile serializacji i deserializacji wolniej niż podejście konstruktora kopiowania?
Woland

75

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. StringS) nie muszą być kopiowane. Na marginesie, powinieneś faworyzować niezmienność z tego powodu.


58

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

4
Jeśli klasa jest ostateczna, jak byś ją przedłużył?
Kumar Manish

1
Klasa @KumarManish MyContainer implementuje instancję Serializable {MyFinalClass; ...}
Matteo T.

Uważam to za świetną odpowiedź. Klon to bałagan
blackbird014,

@MatteoT. w jaki sposób właściwość klasy nie podlegająca serializacji będzie serializowana, instancew tym przypadku niemożliwa do serializacji ?
Farid

40

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.


Jest również dostępny worg.apache.commons.lang.SerializationUtils
Pino

25

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


1
Po prostu interesuje mnie przypadek, że to, co kopiujesz, jest podklasą, ale nadrzędnym jest odniesienie. Czy można zastąpić konstruktor kopiowania?
Pork 'n' Bunny

Dlaczego twoja klasa nadrzędna odwołuje się do swojej podklasy? Czy możesz podać przykład?
Adriaan Koster,

1
klasa publiczna Samochód rozszerza pojazd, a następnie odnosi się do samochodu jako pojazdu. originaList = new ArrayList <Vehicle>; copyList = new ArrayList <Vehicle>; originalList.add (new Car ()); dla (Vehicle pojazdu: VehicleList) {copyList.add (nowy pojazd (pojazd)); }
Pork 'n' Bunny

@AdriaanKoster: Jeśli oryginalna lista zawiera Toyota, Twój kod umieści Carna 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 protectedzapewnić, że będzie on używany tylko do konstruowania obiektów, których dokładny typ odpowiada typowi kopiowanego obiektu).
supercat

Więc jeśli dobrze rozumiem twoją sugestię, metoda fabryczna wywołałaby konstruktora kopii prywatnej? W jaki sposób konstruktor kopii podklasy upewniałby się, że pola nadklasy są inicjowane? Czy możesz podać przykład?
Adriaan Koster

20

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

19

Apache commons oferuje szybki sposób głębokiego klonowania obiektu.

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

1
Działa to tylko dla obiektu, który implementuje Serializable, a także dla wszystkich pól w nim implementujących Serializable.
wonhee

11

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

1
Nieee, nie potrzebujesz nakładki na XML.
egelev

@egeleve Zdajesz sobie sprawę, że odpowiadasz na komentarz z '08 prawda? Nie używam już Java i prawdopodobnie są teraz lepsze narzędzia. Jednak w tym czasie serializowanie do innego formatu, a następnie serializowanie z powrotem wydawało się dobrym hackem - było zdecydowanie nieefektywne.
sankara


10

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

9

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

3
Przestrzegaj konwencji nazewnictwa Java dla własnego i naszego dobra.
Patrick Bergner,

8

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.


7

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.


2
Czy mógłbyś opisać „odpowiednie praktyki projektowe”?
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));
    }
}



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

W tym przypadku klasa MyPerson i MyAddress musi implementować interfejs z możliwością szeregowania


2

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

  }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.