Muszę zaimplementować głębokiego klona w jednym z moich obiektów, który nie ma nadklasy.
Jaki jest najlepszy sposób na obsłużenie czeku CloneNotSupportedException
wyrzuconego przez superklasę (czyli Object
)?
Współpracownik poradził mi, żebym to zrobił w następujący sposób:
@Override
public MyObject clone()
{
MyObject foo;
try
{
foo = (MyObject) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error();
}
// Deep clone member fields here
return foo;
}
Wydaje mi się, że jest to dobre rozwiązanie, ale chciałem przekazać to społeczności StackOverflow, aby sprawdzić, czy są jakieś inne spostrzeżenia, które mogę uwzględnić. Dzięki!
Cloneable
to rzucanieAssertionError
zamiast zwykłego zwykłegoError
jest nieco bardziej wyraziste.Odpowiedzi:
Czy absolutnie musisz użyć
clone
? Większość ludzi zgadza się, że Javaclone
jest zepsuta.Josh Bloch on Design - Copy Constructor versus Cloning
Możesz przeczytać więcej dyskusji na ten temat w jego książce Effective Java 2nd Edition, Item 11: Override
clone
rozsądnie . Zamiast tego zaleca użycie konstruktora kopiującego lub fabryki kopiowania.Następnie napisał kilka stron o tym, jak, jeśli uważasz, że musisz, powinieneś wdrażać
clone
. Ale zakończył tym:Nacisk był jego, nie mój.
Ponieważ jasno wskazałeś, że nie masz innego wyboru, jak tylko wdrożyć
clone
, oto, co możesz zrobić w tym przypadku: upewnij się, żeMyObject extends java.lang.Object implements java.lang.Cloneable
. W takim przypadku możesz zagwarantować, że NIGDY nie złapieszCloneNotSupportedException
. Rzucanie,AssertionError
jak sugerowali niektórzy, wydaje się rozsądne, ale możesz również dodać komentarz wyjaśniający, dlaczego blok catch nigdy nie zostanie wprowadzony w tym konkretnym przypadku .Alternatywnie, jak sugerowali inni, możesz być może zaimplementować
clone
bez wywoływaniasuper.clone
.źródło
super.clone()
w swoich metodach klonowania, podklasa będzie musiała nadpisać tylkoclone()
wtedy, gdy doda nowe pola, których zawartość będzie musiała zostać sklonowana. Jeśli jakakolwiek nadklasa używanew
zamiastsuper.clone()
, to wszystkie podklasy muszą przesłonićclone()
to, czy dodają nowe pola, czy nie.Czasami łatwiej jest zaimplementować konstruktor kopiujący:
Oszczędza Ci to kłopotów z obsługą
CloneNotSupportedException
, działa zfinal
polami i nie musisz martwić się o typ do zwrotu.źródło
Sposób działania twojego kodu jest bardzo zbliżony do „kanonicznego” sposobu jego pisania. Ale rzuciłbym
AssertionError
w pułapkę. Sygnalizuje, że ta linia nigdy nie powinna zostać osiągnięta.źródło
Cloneable
ogólnie jest to zepsuty pomysł, jak wyjaśniono w Efektywna Java. PO już wyraził, że muszą korzystaćCloneable
. Nie mam więc pojęcia, jak inaczej można by ulepszyć moją odpowiedź, może poza jej całkowitym usunięciem.Istnieją dwa przypadki, w których
CloneNotSupportedException
zostanie wyrzucony testament:Cloneable
(zakładając, że faktyczne klonowanie ostatecznie odroczone zostanie doObject
metody clone). Jeśli klasa, w której piszesz tę metodę w implementacjachCloneable
, nigdy się nie wydarzy (ponieważ wszystkie podklasy odziedziczą ją odpowiednio).Cloneable
.Ten drugi przypadek nie może wystąpić w twojej klasie (ponieważ bezpośrednio wywołujesz metodę nadklasy w
try
bloku, nawet jeśli jest wywoływana z wywołania podklasysuper.clone()
), a ten pierwszy nie powinien, ponieważ twoja klasa wyraźnie powinna implementowaćCloneable
.Zasadniczo powinieneś na pewno zarejestrować błąd, ale w tym konkretnym przypadku stanie się to tylko wtedy, gdy zepsujesz definicję swojej klasy. Dlatego traktuj go jak sprawdzoną wersję
NullPointerException
(lub podobną) - nigdy nie zostanie wyrzucona, jeśli twój kod działa.W innych sytuacjach trzeba by być przygotowanym na taką ewentualność - nie ma gwarancji, że dany przedmiot jest Cloneable, więc podczas połowu wyjątek należy podjąć odpowiednie działania w zależności od tego warunku (kontynuacja istniejącego obiektu, wziąć alternatywną strategię klonowania np. serialize-deserialize, wyrzuć,
IllegalParameterException
jeśli twoja metoda wymaga parametru przez cloneable itp.).Edycja : Chociaż ogólnie powinienem zaznaczyć, że tak,
clone()
naprawdę trudno jest poprawnie zaimplementować i trudno jest dzwoniącym wiedzieć, czy wartość zwracana będzie taka, jakiej chcą, podwójnie, jeśli weźmie się pod uwagę klony głębokie i płytkie. Często lepiej jest po prostu całkowicie uniknąć całej sytuacji i użyć innego mechanizmu.źródło
super.clone()
.Użyj serializacji, aby wykonać głębokie kopie. Nie jest to najszybsze rozwiązanie, ale nie zależy od typu.
źródło
Możesz zaimplementować chronione konstruktory kopiujące, takie jak:
źródło
clone()
obiektu zwróconego przez,getMySecondMember()
chyba że mapublic clone
metodę.O ile większość odpowiedzi tutaj jest poprawnych, muszę powiedzieć, że Twoje rozwiązanie jest również sposobem, w jaki robią to faktyczni programiści Java API. (Albo Josh Bloch albo Neal Gafter)
Oto wyciąg z openJDK, klasy ArrayList:
Jak zauważyłeś i inni wspomnieli,
CloneNotSupportedException
nie ma prawie żadnych szans na wyrzucenie, jeśli zadeklarowałeś, że implementujeszCloneable
interfejs.Ponadto, nie ma potrzeby, aby zastąpić metodę, jeśli nie nic nowego w zastąpiona metoda zrobić. Musisz to zmienić tylko wtedy, gdy musisz wykonać dodatkowe operacje na obiekcie lub musisz go upublicznić.
Ostatecznie nadal najlepiej jest tego unikać i robić to w inny sposób.
źródło
źródło
Tylko dlatego, że implementacja Cloneable w Javie jest zepsuta, nie oznacza to, że nie możesz stworzyć własnego.
Jeśli prawdziwym celem OP było stworzenie głębokiego klonu, myślę, że można stworzyć taki interfejs:
następnie użyj wspomnianego wcześniej konstruktora prototypów, aby go zaimplementować:
i inna klasa z polem obiektu AClass:
W ten sposób można łatwo i głęboko sklonować obiekt klasy BClass bez potrzeby używania @SuppressWarnings lub innego sztucznego kodu.
źródło