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.


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.


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


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


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

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

}

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


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.


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.


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

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.