Informacje o klonowalnym języku Java

95

Szukałem kilku tutoriali wyjaśniających o Javie Cloneable, ale nie otrzymałem żadnych dobrych linków, a Stack Overflow i tak staje się bardziej oczywistym wyborem.

Chciałbym wiedzieć, co następuje:

  1. Cloneableoznacza, że ​​możemy mieć klon lub kopię obiektów, implementując Cloneableinterfejs. Jakie są zalety i wady takiego działania?
  2. Jak przebiega klonowanie rekurencyjne, jeśli obiekt jest obiektem złożonym?
marzyciel
źródło
2
Zalety i wady w porównaniu z czym?
Galactus
4
Czytałem, że oznacza to przewagę klasy będącej klonowalną vs. nie. Nie wiem, jak inaczej można by to zinterpretować: S
allyourcode

Odpowiedzi:

159

Pierwszą rzeczą, o której powinieneś wiedzieć, Cloneablejest to, że nie używaj go.

Bardzo trudno jest Cloneablepoprawnie wdrożyć klonowanie , a wysiłek nie jest tego wart.

Zamiast tego użyj innych opcji, takich jak apache-commons SerializationUtils(głęboki klon) lub BeanUtils(płytki-klon) lub po prostu użyj konstruktora kopiującego.

Spójrz tutaj opinie Josha Blocha na temat klonowania za pomocą Cloneable, co wyjaśnia wiele wad tego podejścia. ( Joshua Bloch był pracownikiem firmy Sun i kierował rozwojem wielu funkcji języka Java).

Bozho
źródło
1
Połączyłem
3
Zauważ, że Block mówi, aby nie używać Cloneable. Nie mówi, że nie używaj klonowania (a przynajmniej mam taką nadzieję). Istnieje wiele sposobów implementacji klonowania, które są znacznie bardziej wydajne niż klasy, takie jak SerializationUtils lub BeanUtils, które używają odbicia. Zobacz mój post poniżej, aby zobaczyć przykład.
Charles
jaka jest alternatywa dla konstruktora kopiującego podczas definiowania interfejsów? po prostu dodać metodę kopiowania?
benez
@benez powiedziałbym, że tak. ponieważ java-8 możesz mieć staticmetody w interfejsach, więc po prostu podaj static WhatEverTheInterface copy(WhatEverTheInterface initial)? ale zastanawiam się, co to daje, skoro kopiujesz pola z obiektu podczas klonowania, ale interfejs definiuje tylko metody. chcesz wyjaśnić?
Eugene
40

Sam Cloneable jest niestety tylko interfejsem znacznika, to znaczy: nie definiuje metody clone ().

To, co robi, to zmiana zachowania chronionej metody Object.clone (), która wyrzuci wyjątek CloneNotSupportedException dla klas, które nie implementują Cloneable, i wykona płytką kopię dla elementów członkowskich dla klas, które to robią.

Nawet jeśli jest to zachowanie, którego szukasz, nadal musisz zaimplementować własną metodę clone (), aby je upublicznić.

Podczas implementacji własnego clone (), chodzi o to, aby zacząć od obiektu utworzonego przez super.clone (), który na pewno będzie odpowiedniej klasy, a następnie wykonać dowolną dodatkową populację pól na wypadek, gdyby płytka kopia nie była tym, czym chcesz. Wywołanie konstruktora z funkcji clone () byłoby problematyczne, ponieważ spowodowałoby zerwanie dziedziczenia w przypadku, gdy podklasa chce dodać swoją własną, możliwą do klonowania logikę; jeśli wywoła super.clone (), w tym przypadku otrzyma obiekt niewłaściwej klasy.

Takie podejście omija jednak logikę, która może być zdefiniowana w twoich konstruktorach, co może być potencjalnie problematyczne.

Innym problemem jest to, że wszystkie podklasy, które zapomną nadpisać clone (), automatycznie odziedziczą domyślną płytką kopię, co prawdopodobnie nie jest tym, czego chcesz w przypadku stanu zmiennego (który będzie teraz współdzielony między źródłem a kopią).

Większość programistów nie używa Cloneable z tych powodów i zamiast tego po prostu implementuje konstruktor kopiujący.

Aby uzyskać więcej informacji i potencjalnych pułapek Cloneable, gorąco polecam książkę Effective Java autorstwa Joshua Blocha

Luke Hutteman
źródło
12
  1. Klonowanie wywołuje pozajęzykowy sposób konstruowania obiektów - bez konstruktorów.
  2. Klonowanie wymaga traktowania w jakiś sposób CloneNotSupportedException - lub zawracania sobie głowy kodem klienta, aby go traktował.
  3. Korzyści są niewielkie - po prostu nie musisz ręcznie pisać konstruktora kopiującego.

Więc rozsądnie używaj Cloneable. Nie daje to wystarczających korzyści w porównaniu z wysiłkiem, jaki musisz włożyć, aby zrobić wszystko dobrze.

Vladimir Ivanov
źródło
Jak powiedział Bozho, nie używaj Cloneable. Zamiast tego użyj konstruktora kopiującego. javapractices.com/topic/TopicAction.do?Id=12
Bane
@Bane, co jeśli nie znasz typu obiektu do sklonowania, skąd wiesz, którego konstruktora kopiującego klasy należy wywołać?
Steve Kuo
@Steve: Nie nadążam. Jeśli zamierzasz sklonować obiekt, zakładam, że wiesz już, jaki to jest typ - w końcu masz obiekt, który planujesz sklonować. A jeśli jest sytuacja, w której twój obiekt stracił swój specyficzny typ na bardziej ogólny, czy nie możesz tego ocenić za pomocą prostego „wystąpienia” ???
Bane
4
@Bane: Załóżmy, że masz listę obiektów pochodzących z typu A, może z 10 różnymi typami. Nie wiesz, jaki jest typ każdego obiektu. Używanie instanceof w tym przypadku jest BARDZO złym pomysłem. Jeśli dodasz inny typ, za każdym razem będziesz musiał dodać kolejną instancję testu. A co, jeśli klasy pochodne znajdują się w innym pakiecie, do którego nie masz nawet dostępu? Klonowanie to powszechny wzorzec. Tak, implementacja Java jest zła, ale istnieje wiele sposobów obejścia tego problemu, które będą działać dobrze. Konstruktor kopiujący nie jest równoważną operacją.
Charles
@Charles: Wobec braku szczegółowego przykładu i niedawnego doświadczenia w radzeniu sobie z tego rodzaju problemami, będę musiał odnieść się do Blocha. Punkt 11. Jest długi i trochę trudny do odczytania, ale w zasadzie mówi: „unikaj klonowania, kiedy tylko możesz, konstruktorzy kopiujący są twoimi przyjaciółmi”.
Bane
7

Klonowanie to podstawowy paradygmat programowania. Fakt, że Java mogła źle zaimplementować go pod wieloma względami, wcale nie zmniejsza potrzeby klonowania. I łatwo jest wdrożyć klonowanie, które będzie działać tak, jak chcesz, płytko, głęboko, mieszane, cokolwiek. Możesz nawet użyć nazwy clone dla funkcji i nie implementować Cloneable, jeśli chcesz.

Załóżmy, że mam klasy A, B i C, gdzie B i C pochodzą od A. Jeśli mam listę obiektów typu A w następujący sposób:

ArrayList<A> list1;

Ta lista może zawierać obiekty typu A, B lub C. Nie wiesz, jakiego typu są te obiekty. Nie możesz więc skopiować listy w ten sposób:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(new A(a));
}

Jeśli obiekt jest rzeczywiście typu B lub C, nie otrzymasz właściwej kopii. A co, jeśli A jest abstrakcyjne? Niektórzy ludzie zasugerowali to:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    if(a instanceof A) {
        list2.add(new A(a));
    } else if(a instanceof B) {
        list2.add(new B(a));
    } else if(a instanceof C) {
        list2.add(new C(a));
    }
}

To bardzo, bardzo zły pomysł. Co się stanie, jeśli dodasz nowy typ pochodny? Co jeśli B lub C znajdują się w innym pakiecie i nie masz do nich dostępu w tej klasie?

Co chciałbyś zrobić, to:

ArrayList<A> list2 = new ArrayList<A>();
for(A a : list1) {
    list2.add(a.clone());
}

Wiele osób wskazało, dlaczego podstawowa implementacja klona w Javie jest problematyczna. Ale można to łatwo pokonać w ten sposób:

W klasie A:

public A clone() {
    return new A(this);
}

W klasie B:

@Override
public B clone() {
    return new B(this);
}

W klasie C:

@Override
public C clone() {
    return new C(this):
}

Nie implementuję Cloneable, po prostu używam tej samej nazwy funkcji. Jeśli ci się to nie podoba, nazwij to inaczej.

Charles
źródło
Właśnie to widziałem po odpowiedzi na Twój komentarz w oddzielnej odpowiedzi; Widzę teraz, dokąd zmierzasz, jednak dwie rzeczy: 1) OP zapytał konkretnie o użycie Cloneable (nie o ogólną koncepcję klonowania) i 2) rozdzielasz tutaj trochę włosy, próbując rozróżnić konstruktor kopiujący oraz ogólna koncepcja klonowania. Pomysł, który tu przekazujesz, jest ważny, ale w jego korzeniu używasz tylko konstruktora kopiującego. ;)
Bane
Chociaż chcę powiedzieć, że zgadzam się z twoim podejściem tutaj, polegającym na włączeniu A # copyMethod (), zamiast zmuszać użytkownika do bezpośredniego wywoływania konstruktora kopiującego.
Bane
5

A) Nie ma wielu zalet clone w porównaniu z konstruktorem kopiującym. Prawdopodobnie największą z nich jest możliwość stworzenia nowego obiektu o dokładnie tym samym typie dynamicznym (przy założeniu, że zadeklarowany typ jest klonowalny i ma publiczną metodę klonowania).

B) Domyślny klon tworzy płytką kopię i pozostanie płytką kopią, chyba że implementacja klonu to zmieni. Może to być trudne, zwłaszcza jeśli twoja klasa ma ostatnie pola

Bozho ma rację, klon może być trudny do uzyskania. Konstruktor kopii / fabryka będzie spełniać większość potrzeb.

ILMTitan
źródło
0

Jakie są wady Cloneable?

Klonowanie jest bardzo niebezpieczne, jeśli obiekt który kopiujesz ma kompozycję, musisz pomyśleć o poniżej możliwym efekcie ubocznym w tym przypadku, ponieważ klon tworzy płytką kopię:

Powiedzmy, że masz jeden obiekt do obsługi manipulacji związanych z db. Powiedzmy, że obiekt ma Connectionobiekt jako jedną z właściwości.

Więc gdy ktoś tworzy klona originalObject, tworzonego obiektu, powiedzmy cloneObject. Tutaj originalObjecti cloneObjecttrzymaj to samo odniesienie dla Connectionobiektu.

Powiedzmy, że originalObjectzamyka Connectionobiekt, więc teraz cloneObjectnie będzie działać, ponieważ connectionobiekt był między nimi współdzielony i został aktywnie zamknięty przez originalObject.

Podobny problem może wystąpić, jeśli powiedzmy, że chcesz sklonować obiekt, który ma IOStream jako właściwość.

Jak przebiega klonowanie rekurencyjne, jeśli obiekt jest obiektem złożonym?

Cloneable wykonuje płytką kopię. Oznacza to, że dane oryginalnego obiektu i obiektu sklonowanego będą wskazywać na to samo odniesienie / pamięć. w przeciwieństwie do głębokiej kopii, dane z pamięci obiektu oryginalnego są kopiowane do pamięci obiektu klonowanego.

027
źródło
Twój ostatni akapit jest bardzo zdezorientowany. Cloneablenie wykonuje kopii, Object.clonerobi. "Dane z pamięci oryginalnego obiektu są kopiowane do pamięci klonowanego obiektu" to właśnie to Object.clonerobi. Aby opisać głębokie kopiowanie, musisz porozmawiać o pamięci obiektów, do których istnieją odniesienia.
aioobe