Kopiowanie obiektów w Javie to proces tworzenia nowego obiektu o takim samym stanie jak istniejący egzemplarz. Historia kopiowania stała się aktualna od samego początku rozwoju Javy, gdy pojawiła się potrzeba duplikowania obiektów bez ręcznego ich odtwarzania. Sama Java nie dostarcza uniwersalnego sposobu kopiowania obiektów, zamiast tego wykorzystuje kontrakt clone() oraz wzorzec konstruktora kopiującego.
Metoda clone() została wprowadzona w interfejsie Cloneable, aby zapewnić standardowy mechanizm klonowania obiektów, jednak jej implementacja przynosi wiele niuansów i często prowadzi do pojawiania się błędów.
Głównym problemem jest brak prawdziwego głębokiego kopiowania "z pudełka" oraz możliwość wystąpienia nieprzewidywalnego zachowania przy powierzchownym klonowaniu (shallow copy). Konstruktory kopiujące pozwalają na implementację zarówno powierzchownego, jak i głębokiego kopiowania, ale wymagają jawnej realizacji.
Dla bezpiecznego i poprawnego kopiowania złożonych obiektów preferowane jest stosowanie konstruktora kopiującego lub metody fabrycznej, podczas gdy clone() należy stosować ostrożnie — tylko jeśli obiekt rzeczywiście spełnia ten kontrakt.
Przykład kodu:
// Powierzchowne klonowanie przez clone() public class Address implements Cloneable { String city; public Address(String city) { this.city = city; } @Override public Address clone() throws CloneNotSupportedException { return (Address) super.clone(); } } public class Person implements Cloneable { String name; Address address; public Person(String name, Address address) { this.name = name; this.address = address; } @Override public Person clone() throws CloneNotSupportedException { Person copy = (Person) super.clone(); copy.address = address.clone(); // głębokie kopiowanie address return copy; } } // Konstruktory kopiujące public class Person { String name; Address address; public Person(Person other) { this.name = other.name; this.address = new Address(other.address.city); } }
Kluczowe cechy:
clone() wymaga implementacji interfejsu Cloneable oraz nadpisania metody clone.Pytanie 1: Co się stanie, jeśli klasa nie implementuje Cloneable, gdy wywołasz clone()?
clone() rzuci CloneNotSupportedException, jeśli obiekt nie implementuje interfejsu Cloneable.
Pytanie 2: Czy klonowanie przez clone() jest głębokie domyślnie?
Nie, domyślnie zrealizowane jest tylko powierzchowne klonowanie, wszystkie referencje do obiektów są kopiowane tak jak są.
Pytanie 3: Czy można obejść się bez konstruktora kopiującego dla głębokiego klonowania?
Nie, jeśli chcesz kontrolować proces, musisz jawnie definiować kopiowanie zagnieżdżonych obiektów przez konstruktor lub ręcznie w clone().
Programista zastosował standardowy clone() w klasie Order z polem List<Product>. W rezultacie przy zmianie listy produktów w kopii, zmieniły się także produkty w oryginale, co spowodowało błąd.
Zalety:
Wady:
W firmie wdrożono konstruktor kopiujący dla wszystkich encji domenowych i dodatkowo pokryto testami proces klonowania dla złożonych zagnieżdżonych obiektów.
Zalety:
Wady: