Zrobiłem szybkie wyszukiwanie w Google na temat implementacji clone () w Javie i znalazłem: http://www.javapractices.com/topic/TopicAction.do?Id=71
Zawiera następujący komentarz:
konstruktory kopiujące i statyczne metody fabryczne stanowią alternatywę dla klonowania i są znacznie łatwiejsze do wdrożenia.
Chcę tylko zrobić głęboką kopię. Wydaje się, że wdrożenie clone () ma wiele sensu, ale ten artykuł wysoko oceniany w Google trochę mnie przeraża.
Oto problemy, które zauważyłem:
Konstruktory kopiujące nie działają z typami generycznymi.
Oto pseudokod, który się nie kompiluje.
public class MyClass<T>{
..
public void copyData(T data){
T copy=new T(data);//This isn't going to work.
}
..
}
Przykład 1: Używanie konstruktora kopiującego w klasie ogólnej.
Metody fabryczne nie mają standardowych nazw.
Miło jest mieć interfejs do kodu wielokrotnego użytku.
public class MyClass<T>{
..
public void copyData(T data){
T copy=data.clone();//Throws an exception if the input was not cloneable
}
..
}
Przykład 2: Używanie clone () w klasie ogólnej.
Zauważyłem, że klon nie jest metodą statyczną, ale czy nadal nie byłoby konieczne tworzenie głębokich kopii wszystkich chronionych pól? Podczas implementacji clone () dodatkowy wysiłek związany z rzucaniem wyjątków w nieklonowalnych podklasach wydaje mi się trywialny.
Czy coś mi brakuje? Wszelkie spostrzeżenia będą mile widziane.
Odpowiedzi:
Zasadniczo klon jest uszkodzony . Nic nie będzie łatwo działać z typami generycznymi. Jeśli masz coś takiego (skróconego, aby przekazać punkt):
public class SomeClass<T extends Copyable> { public T copy(T object) { return (T) object.copy(); } } interface Copyable { Copyable copy(); }
Następnie z ostrzeżeniem kompilatora możesz wykonać zadanie. Ponieważ typy generyczne są usuwane w czasie wykonywania, coś, co wykonuje kopię, będzie miało ostrzeżenie kompilatora generujące rzutowanie.
W tym przypadku nie da się tego uniknąć.. W niektórych przypadkach można tego uniknąć (dzięki, kb304), ale nie we wszystkich. Rozważ przypadek, w którym musisz obsługiwać podklasę lub nieznaną klasę implementującą interfejs (na przykład iterowałeś przez kolekcję elementów do kopiowania, które niekoniecznie generowały tę samą klasę).źródło
Copyable<T>
w dół, ponieważ odpowiedź kd304 ma znacznie większy sens.Istnieje również wzorzec Builder. Aby uzyskać szczegółowe informacje, zobacz Efektywna Java.
Nie rozumiem twojej oceny. W konstruktorze kopiującym jesteś w pełni świadomy typu, dlaczego istnieje potrzeba używania typów generycznych?
public class C { public int value; public C() { } public C(C other) { value = other.value; } }
Ostatnio pojawiło się podobne pytanie .
public class G<T> { public T value; public G() { } public G(G<? extends T> other) { value = other.value; } }
Próbka do uruchomienia:
public class GenTest { public interface Copyable<T> { T copy(); } public static <T extends Copyable<T>> T copy(T object) { return object.copy(); } public static class G<T> implements Copyable<G<T>> { public T value; public G() { } public G(G<? extends T> other) { value = other.value; } @Override public G<T> copy() { return new G<T>(this); } } public static void main(String[] args) { G<Integer> g = new G<Integer>(); g.value = 1; G<Integer> f = g.copy(); g.value = 2; G<Integer> h = copy(g); g.value = 3; System.out.printf("f: %s%n", f.value); System.out.printf("g: %s%n", g.value); System.out.printf("h: %s%n", h.value); } }
źródło
Java nie ma konstruktorów kopiujących w tym samym sensie, co C ++.
Możesz mieć konstruktor, który pobiera obiekt tego samego typu jako argument, ale niewiele klas to obsługuje. (mniejsza niż liczba, która obsługuje klonowanie)
Dla klonu ogólnego mam metodę pomocniczą, która tworzy nową instancję klasy i kopiuje pola z oryginału (płytka kopia) za pomocą odbić (właściwie coś w rodzaju odbić, ale szybciej)
W przypadku głębokiej kopii prostym podejściem jest serializacja obiektu i deserializacja go.
BTW: Moja sugestia to użycie niezmiennych obiektów, wtedy nie będziesz musiał ich klonować. ;)
źródło
String
na przykład jest niezmiennym obiektem i możesz przekazać String bez konieczności kopiowania go.Myślę, że odpowiedź Yishai można poprawić, więc nie możemy mieć żadnego ostrzeżenia z następującym kodem:
public class SomeClass<T extends Copyable<T>> { public T copy(T object) { return object.copy(); } } interface Copyable<T> { T copy(); }
W ten sposób klasa która musi implementować interfejs Copyable musi wyglądać następująco:
public class MyClass implements Copyable<MyClass> { @Override public MyClass copy() { // copy implementation ... } }
źródło
interface Copyable<T extends Copyable>
co jest najbardziej zbliżone do typu własnego, który można zakodować w Javie.interface Copyable<T extends Copyable<T>>
, prawda? ;)Poniżej znajduje się kilka wad, z powodu których wielu programistów nie używa
Object.clone()
Object.clone()
metody wymaga od nas dodania wielu składni do naszego kodu, takich jakCloneable
interfejs implementacji , zdefiniowanieclone()
metody i uchwytu,CloneNotSupportedException
a na koniec wywołanieObject.clone()
i rzutowanie tego obiektu.Cloneable
interfejs nie maclone()
metody, jest to interfejs znacznika i nie ma w nim żadnej metody, a mimo to musimy go zaimplementować tylko po to, aby powiedzieć JVM, że możemy wykonaćclone()
na naszym obiekcie.Object.clone()
jest chroniony, więc musimy zapewnić własneclone()
i pośrednie połączenieObject.clone()
z niego.Object.clone()
nie wywołujemy żadnego konstruktora.clone()
metodę w klasiePerson
potomnej, to np. Wszystkie jej nadklasy powinny definiowaćclone()
w nich metodę lub dziedziczyć ją z innej klasy nadrzędnej, w przeciwnym raziesuper.clone()
łańcuch się nie powiedzie.Object.clone()
obsługują tylko płytką kopię, więc pola referencyjne naszego nowo sklonowanego obiektu będą nadal zawierać obiekty, które zawierały pola naszego oryginalnego obiektu. Aby temu zaradzić, musimy wdrożyćclone()
w każdej klasie, do której odwołuje się nasza klasa, a następnie sklonować je oddzielnie w naszejclone()
metodzie, jak w poniższym przykładzie.Object.clone()
ponieważ końcowe pola można zmienić tylko za pomocą konstruktorów. W naszym przypadku, jeśli chcemy, aby każdyPerson
obiekt był unikalny według identyfikatora, otrzymamy zduplikowany obiekt, jeśli użyjemy,Object.clone()
ponieważObject.clone()
nie wywoła konstruktora, aid
pola końcowego nie można zmodyfikowaćPerson.clone()
.Konstruktory kopiujące są lepsze niż
Object.clone()
ponieważ onePrzeczytaj więcej na temat klonowania języka Java - konstruktor kopiowania a klonowanie
źródło
Zwykle clone () działa w tandemie z chronionym konstruktorem kopiowania. Dzieje się tak, ponieważ clone (), w przeciwieństwie do konstruktora, może być wirtualne.
W treści klasy dla klasy Derived from super class Base mamy
class Derived extends Base { }
Tak więc, w najbardziej uproszczonym przypadku, można dodać do tego wirtualnego konstruktora kopiującego za pomocą funkcji clone (). (W C ++ Joshi zaleca clone jako konstruktor wirtualnego kopiowania).
protected Derived() { super(); } protected Object clone() throws CloneNotSupportedException { return new Derived(); }
To staje się bardziej skomplikowane, jeśli chcesz wywołać super.clone () zgodnie z zaleceniami i musisz dodać tych członków do klasy, możesz spróbować tego
final String name; Address address; /// This protected copy constructor - only constructs the object from super-class and /// sets the final in the object for the derived class. protected Derived(Base base, String name) { super(base); this.name = name; } protected Object clone() throws CloneNotSupportedException { Derived that = new Derived(super.clone(), this.name); that.address = (Address) this.address.clone(); }
Teraz, jeśli egzekucja, masz
Base base = (Base) new Derived("name");
i wtedy to zrobiłeś
wywołałoby to clone () w klasie pochodnej (powyższej), wywołałoby super.clone () - które może być zaimplementowane lub nie, ale zaleca się wywołanie tego. Następnie implementacja przekazuje wynik super.clone () do chronionego konstruktora kopiującego, który pobiera Base i przekazuje do niego wszystkie końcowe elementy członkowskie.
Ten konstruktor kopiujący następnie wywołuje konstruktor kopiujący superklasy (jeśli wiesz, że ma taki konstruktor) i ustawia finały.
Wracając do metody clone (), ustawiasz dowolne elementy, które nie są ostateczne.
Wnikliwi czytelnicy zauważą, że jeśli masz konstruktora kopiującego w Base, zostanie on wywołany przez super.clone () - i zostanie wywołany ponownie po wywołaniu superkonstruktora w chronionym konstruktorze, więc możesz wywołać funkcję super-kopiujący konstruktor dwukrotnie. Miejmy nadzieję, że jeśli blokuje zasoby, będzie o tym wiedział.
źródło
Jednym ze wzorców, który może Ci się przydać, jest kopiowanie na poziomie fasoli. Zasadniczo używasz konstruktora bez arg i wywołujesz różne metody ustawiające w celu udostępnienia danych. Możesz nawet użyć różnych bibliotek właściwości bean, aby stosunkowo łatwo ustawić właściwości. To nie to samo, co wykonanie clone (), ale z wielu praktycznych powodów jest w porządku.
źródło
Interfejs Cloneable jest zepsuty w tym sensie, że jest bezużyteczny, ale klon działa dobrze i może prowadzić do lepszej wydajności dla dużych obiektów - 8 pól i więcej, ale wtedy nie przejdzie analizy ucieczki. więc preferowane jest używanie konstruktora kopiującego przez większość czasu. Używanie clone na array jest szybsze niż Arrays.copyOf, ponieważ gwarantujemy, że długość będzie taka sama.
więcej szczegółów tutaj https://arnaudroger.github.io/blog/2017/07/17/deep-dive-clone-vs-copy.html
źródło
Jeśli ktoś nie jest w 100% świadomy wszystkich dziwactw
clone()
, radziłbym trzymać się od tego z daleka. Nie powiedziałbym, że toclone()
zepsute. Powiedziałbym: używaj go tylko wtedy, gdy jesteś całkowicie pewien, że jest to najlepsza opcja. Konstruktor kopiujący (lub metoda fabryczna, myślę, że nie ma to większego znaczenia) jest łatwy do napisania (może długi, ale łatwy), kopiuje tylko to, co chcesz skopiować, i kopiuje tak, jak chcesz. Możesz go dopasować do swoich potrzeb.Dodatkowo: łatwo jest debugować, co się dzieje, gdy wywołujesz metodę konstruktora kopiującego / fabryki.
I
clone()
nie tworzy „głębokiej” kopii twojego obiektu po wyjęciu z pudełka, zakładając, że masz na myśli, że nie tylko odniesienia (np. Do aCollection
) są kopiowane. Ale przeczytaj więcej o głębokiej i płytkiej kopii tutaj: Głęboka kopia, płytka kopia, klonźródło
Brakuje ci tego, że klon tworzy płytkie kopie domyślnie i zgodnie z konwencją, a tworzenie głębokich kopii jest generalnie niewykonalne.
Problem polega na tym, że tak naprawdę nie można tworzyć głębokich kopii cyklicznych wykresów obiektów bez możliwości śledzenia, które obiekty zostały odwiedzone. clone () nie zapewnia takiego śledzenia (ponieważ musiałby to być parametr .clone ()), a zatem tworzy tylko płytkie kopie.
Nawet jeśli twój własny obiekt wywołuje .clone dla wszystkich swoich członków, nadal nie będzie to głęboka kopia.
źródło
SuperDuperList<T>
lub pochodną, to klonowanie powinno dać nową instancję tego samego typu, co sklonowana; powinien być oddzielony od oryginału, ale powinien odnosić się do tych samychT
, w tej samej kolejności, co oryginał. Nic, co zostanie zrobione na którejkolwiek z list od tego momentu, nie powinno wpłynąć na tożsamości obiektów przechowywanych na drugiej. Nie znam żadnej użytecznej „konwencji”, która wymagałaby, aby kolekcja ogólna wykazywała jakiekolwiek inne zachowanie.