Java: zalecane rozwiązanie do głębokiego klonowania / kopiowania instancji

176

Zastanawiam się, czy istnieje zalecany sposób wykonywania głębokiego klonowania / kopiowania instancji w Javie.

Mam na myśli 3 rozwiązania, ale niektórych mogę przegapić i chciałbym poznać Twoją opinię

edytuj: dołącz propositon Bohzo i doprecyzuj pytanie: bardziej chodzi o głębokie klonowanie niż płytkie klonowanie.

Zrób to sam:

zakoduj clone ręcznie właściwości po właściwościach i sprawdź, czy mutowalne instancje również są klonowane.
pro:
- kontrola tego, co zostanie wykonane
-
wady szybkiego wykonania :
- żmudne pisanie i konserwacja
- podatne na błędy (błąd kopiowania / wklejania, brakująca właściwość, ponownie przypisana zmienna właściwość)

Użyj odbicia:

Z własnymi narzędziami do refleksji lub z zewnętrznym pomocnikiem (takim jak fasolka dżakarta) łatwo jest napisać ogólną metodę kopiowania, która wykona zadanie w jednej linii.
pro:
- łatwy do napisania
- brak konserwacji
wady:
- mniejsza kontrola nad tym, co się dzieje
- podatne na błędy w przypadku obiektów mutowalnych, jeśli narzędzie odbicia nie klonuje również obiektów podrzędnych
- wolniejsze wykonywanie

Użyj struktury klonowania:

Użyj frameworka, który zrobi to za Ciebie, takiego jak:
commons-lang SerializationUtils
Java Deep Cloning Library
Dozer
Kryo

pro:
- to samo, co odbicie
- większa kontrola nad tym, co dokładnie zostanie sklonowane.
minusy:
- każda zmienna instancja jest w pełni sklonowana, nawet na końcu hierarchii
- może być bardzo powolna do wykonania

Użyj instrumentacji kodu bajtowego, aby napisać klon w czasie wykonywania

javassit , BCEL lub cglib mogą być użyte do wygenerowania dedykowanego klonera tak szybko, jak napisane jedną ręką. Ktoś zna bibliotekę używającą do tego celu jednego z tych narzędzi?

Co tu przegapiłem?
Który byś polecił?

Dzięki.

Guillaume
źródło
1
najwyraźniej Java Deep Cloning Library została przeniesiona tutaj: code.google.com/p/cloning
Mr_and_Mrs_D

Odpowiedzi:

155

W przypadku głębokiego klonowania (klonuje całą hierarchię obiektów):

  • commons-lang SerializationUtils - używając serializacji - jeśli kontrolujesz wszystkie klasy i możesz wymusić implementację Serializable.

  • Java Deep Cloning Library - przy użyciu odbicia - w przypadkach, gdy klasy lub obiekty, które chcesz sklonować, są poza Twoją kontrolą (biblioteka innej firmy) i nie możesz ich zaimplementować Serializablelub w przypadkach, gdy nie chcesz implementować Serializable.

W przypadku płytkiego klonowania (klonuje tylko właściwości pierwszego poziomu):

Celowo pominąłem opcję „zrób to sam” - powyższe API zapewnia dobrą kontrolę nad tym, co klonować, a czego nie (na przykład używając transient, lub String[] ignoreProperties), więc ponowne wynalezienie koła nie jest preferowane.

Bozho
źródło
Dzięki Bozho, to cenne. I zgadzam się z tobą co do opcji DIY! Czy kiedykolwiek próbowałeś serializacji commons i / lub biblioteki głębokiego klonowania? A co z perfami?
Guillaume
tak, użyłem wszystkich powyższych opcji, z powyższych powodów :) tylko biblioteka klonująca miała pewne problemy, gdy zaangażowane były serwery proxy CGLIB i brakowało niektórych pożądanych funkcji, ale myślę, że należy to teraz naprawić.
Bozho
Hej, jeśli moja jednostka jest dołączona i mam leniwe rzeczy, czy SerializationUtils sprawdza bazę danych pod kątem leniwych właściwości? Bo tego właśnie chcę, a tak nie jest!
Cosmin Cosmin
jeśli masz aktywną sesję - tak.
Bozho
@Bozho Masz na myśli, że jeśli wszystkie obiekty w ziarnie są implementowane z możliwością serializacji, org.apache.commons.beanutils.BeanUtils.cloneBean (obj) wykona głęboką kopię?
przeskocz
36

Książka Joshuy Blocha zawiera cały rozdział zatytułowany „Punkt 10: Zastąp klonowanie rozsądnie”, w którym wyjaśnia, dlaczego zastępowanie klonu w większości jest złym pomysłem, ponieważ specyfikacja Javy stwarza wiele problemów.

Zapewnia kilka alternatyw:

  • Użyj wzorca fabrycznego zamiast konstruktora:

         public static Yum newInstance(Yum yum);
  • Użyj konstruktora kopiującego:

         public Yum(Yum yum);

Wszystkie klasy kolekcji w Javie obsługują konstruktor kopiujący (np. New ArrayList (l);)

LeWoody
źródło
1
Zgoda. W moim projekcie zdefiniowałem Copyableinterfejs zawierający getCopy()metodę. Po prostu użyj ręcznie wzoru prototypu.
gpampara
Cóż, nie pytałem o klonowalny interfejs, ale jak wykonać głęboką operację klonowania / kopiowania. W przypadku konstruktora lub fabryki nadal musisz utworzyć nową instancję ze swojego źródła.
Guillaume,
@Guillaume Myślę, że musisz uważać, używając słów deep clone / copy. Klonowanie i kopiowanie w Javie NIE oznacza tego samego. Specyfikacja Javy ma więcej do powiedzenia na ten temat ... Myślę, że potrzebujesz dokładnej kopii tego, co mogę powiedzieć.
LeWoody
OK Specyfikacja Java jest dokładna co do tego, czym jest klon ... Ale możemy również mówić o klonie w bardziej powszechnym znaczeniu ... Na przykład jedna z bibliotek polecanych przez bohzo nosi nazwę `` Java Deep Cloning Library '' ...
Guillaume
2
@LWoodyiii ta newInstance()metoda i Yumkonstruktor zrobiłby głęboką kopię lub płytką kopię?
Geek
9

Od wersji 2.07 Kryo obsługuje płytkie / głębokie klonowanie :

Kryo kryo = new Kryo();
SomeClass someObject = ...
SomeClass copy1 = kryo.copy(someObject);
SomeClass copy2 = kryo.copyShallow(someObject);

Kryo jest szybki, na końcu ich strony można znaleźć listę firm, które używają go w produkcji.

Andrey Chaschev
źródło
5

Użyj XStream toXML / fromXML w pamięci. Niezwykle szybki, istnieje już od dłuższego czasu i rośnie w siłę. Obiekty nie muszą być serializowane i nie musisz używać odbicia (chociaż XStream to robi). XStream potrafi rozróżniać zmienne wskazujące na ten sam obiekt i nie przypadkowo tworzy dwie pełne kopie instancji. Wiele takich szczegółów zostało dopracowanych przez lata. Używam go od wielu lat i jest to dobry pomysł. Jest tak łatwy w użyciu, jak możesz sobie wyobrazić.

new XStream().toXML(myObj)

lub

new XStream().fromXML(myXML)

Klonować,

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

Bardziej zwięźle:

XStream x = new XStream();
Object myClone = x.fromXML(x.toXML(myObj));
Ranx
źródło
3

W przypadku skomplikowanych obiektów i gdy wydajność nie jest znacząca, używam gson do serializacji obiektu do tekstu JSON , a następnie deserializacji tekstu, aby uzyskać nowy obiekt.

gson, który oparty na odbiciu będzie działał w większości przypadków, z wyjątkiem tego, że transientpola nie zostaną skopiowane, a obiekty z cyklicznym odwołaniem z przyczyną 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);

}
tiboo
źródło
2

Zależy.

Aby uzyskać szybkość, użyj DIY. Aby uzyskać kuloodporność, użyj odbicia.

BTW, serializacja to nie to samo co refl, ponieważ niektóre obiekty mogą zapewniać nadpisane metody serializacji (readObject / writeObject) i mogą być wadliwe

Yoni Roit
źródło
1
odbicie nie jest kuloodporne: może prowadzić do sytuacji, w której sklonowany obiekt ma odniesienie do twojego źródła ... Jeśli zmieni się źródło, klon też się zmieni!
Guillaume
1

Poleciłbym metodę DIY, która w połączeniu z dobrą metodą hashCode () i equals () powinna być łatwa do sprawdzenia w teście jednostkowym.

Dominik Sandjaja
źródło
no cóż, leniwy ja dużo rusza, tworząc taki fałszywy kod. Ale to wygląda na mądrzejszą ścieżkę ...
Guillaume
2
przepraszam, ale DIY jest drogą do zrobienia TYLKO wtedy, gdy żadne inne rozwiązanie nie jest dla Ciebie odpowiednie ... co prawie nigdy nie jest
Bozho
1

Sugerowałbym nadpisanie Object.clone (), najpierw wywołanie super.clone (), a następnie wywołanie ref = ref.clone () na wszystkich odniesieniach, które chcesz głęboko skopiować. To mniej więcej podejście Zrób to sam, ale wymaga trochę mniej kodowania.

x4u
źródło
2
To jeden z wielu problemów (zepsutej) metody clone: ​​w hierarchii klas zawsze musisz wywołać super.clone (), o czym łatwo zapomnieć, dlatego wolałbym użyć konstruktora kopiującego.
helpermethod
0

W przypadku głębokiego klonowania zaimplementuj Serializable na każdej klasie, którą chcesz sklonować w ten sposób

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

A następnie użyj tej funkcji:

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

lubię to: Obj newObject = (Obj)deepClone(oldObject);

Alexander Maslew
źródło