Java programátor expert
Klonovanie objektov v Jave je častá operácia, ktorá môže byť pre menej skúsených programátorov na prvý pohľad mätúca. Vysvetlíme ti, čo znamená klonovanie, prečo je potrebné a v čom sa líšia dva základné prístupy – shallow copy a deep copy.

V článku sa dozvieš:
Keď v Jave pracujeme s objektmi, často nepotrebujeme vytvoriť úplne nový objekt od začiatku, ale chceme skopírovať stav existujúceho objektu. Klonovanie objektov nám umožňuje vytvoriť kópiu objektu, pričom záleží na konkrétnom spôsobe, ako sa kopírujú vnútorné dáta (resp. stav objektu).
Plytké kopírovanie (resp. plytké klonovanie) vytvorí nový objekt, avšak vnútorné referencie na iné objekty zostanú rovnaké. To znamená, že ak sa pôvodný objekt skladá z viacerých objektov (napríklad pole objektov alebo zložitejšie dátové štruktúry), tento prístup klonovania len skopíruje referencie na tieto vnútorné objekty. Ak zmeníme niektorý z týchto vnútorných objektov, zmena sa prejaví aj v kópii.
Predstav si, že máš objekt Osoba, ktorý obsahuje inštanciu objektu Adresa, pomocou plytkého klonovania síce z objektu Osoba naklonuješ nový objekt Osoba, ale oba budú zdieľať referenciu na rovnaký objekt Adresa. V takom prípade je to presne to, čo chceš dosiahnuť, ak obe osoby bývajú na rovnakej adrese (napr. členovia tej istej rodiny). Ak zmeníš hociktorý atribút adresy, zmena sa prejaví v oboch objektoch Osoba, čiže v pôvodnom aj naklonovanom. Samozrejme, ak sa jedna z osôb presťahuje, zmena v adrese zdieľanej oboma osobami prostredníctvom referencie bude viesť k chybným výsledkom.
Plytké klonovanie má niekoľko praktických výhod:
Na druhej strane, shallow copy má aj svoje nevýhody:
Preto ak potrebuješ úplne oddelenú (nezávislú) kópiu so zmenou vnútorného stavu, plytké kopírovanie (klonovanie) nie je vhodné.
Hĺbkové kopírovanie (resp. hĺbkové klonovanie) vytvorí nový objekt a rekurzívne vytvorí nové kópie všetkých vnútorných objektov. Výsledkom je úplne nezávislá kópia pôvodného objektu, kde zmeny v jednom objekte neovplyvnia druhý.
V príklade klonovania osoby pomocou hĺbkového klonovania dosiahneme, že objekt Adresa (resp. iné objekty alebo neprimitívne dátové typy) sa pre klonovanú osobu tiež naklonuje.
Medzi hlavné výhody hĺbkového klonovania patria:
Deep copy má však aj svoje nevýhody:
Pozri si jednoduchý príklad, ako implementovať shallow copy a deep copy v Jave. Najskôr je potrebné vytvoriť objekty Person a Address a potom porovnáš referencie a ekvivalentnosť objektov pri oboch prístupoch kopírovania.
Person.java
class Person implements Cloneable {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
//Shallow copy
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/*//Deep copy
@Override
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = new Address(this.address.getCity());
return cloned;
}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return name.equals(person.name) && address.equals(person.address);
}
}
Address.java
class Address {
private String city;
public Address(String city) {
this.city = city;
}
public String getCity() {
return city;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Address address = (Address) obj;
return city.equals(address.city);
}
}
Main.java
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("Bratislava");
Person personOrig = new Person("Janko Mrkvička", address);
// Shallow copy
Person personShallowCopy = (Person) personOrig.clone();
// Deep copy
Person personDeepCopy = new Person(personOrig.getName(), new Address(personOrig.getAddress().getCity()));
System.out.println("Shallow copy compare:");
System.out.println("personOrig == personShallowCopy: " + (personOrig == personShallowCopy)); // false
System.out.println("personOrig.getAddress() == shallowCopy.getAddress(): " + (personOrig.getAddress() == personShallowCopy.getAddress())); // true
System.out.println("personOrig.equals(personShallowCopy): " + personOrig.equals(personShallowCopy)); // true
System.out.println("Deep copy compare:");
System.out.println("personOrig == deepCopy: " + (personOrig == personDeepCopy)); // false
System.out.println("personOrig.getAddress() == deepCopy.getAddress(): " + (personOrig.getAddress() == personDeepCopy.getAddress())); // false
System.out.println("personOrig.equals(deepCopy): " + personOrig.equals(personDeepCopy)); // true
}
}
Tento program demonštruje rozdiel medzi shallow copy a deep copy. Pri shallow copy zostáva referencia na Address rovnaká, zatiaľ čo pri deep copy sa vytvorí nová inštancia Address, čo je vidieť na porovnaní Java objektov pomocou referencií (==).
Výstup programu je:
"C:\Program Files\Java\jdk-23\bin\java.exe" "-javaagent:C:
Shallow copy compare:
personOrig == personShallowCopy: false
personOrig.getAddress() == shallowCopy.getAddress(): true
personOrig.equals(shallowCopy): true
Deep copy compare:
personOrig == deepCopy: false
personOrig.getAddress() == deepCopy.getAddress(): false
personOrig.equals(deepCopy): true
Process finished with exit code 0
Shallow copy použi vtedy, keď máš istotu, že vnútorné objekty sa nemajú meniť alebo ak sú zmeny žiaduce (napríklad pre zvýšenie výkonu a zníženie pamäťových nárokov).
Deep copy zvoľ v prípadoch, kde je potrebná úplná nezávislosť objektov, napríklad pri práci s multithreadingom alebo keď zmeny v jednom objekte musia byť úplne izolované.
| Shallow Copy | Deep Copy | |
| Vytvorenie kópie | Vytvorí nový objekt, ale skopíruje referencie polí pôvodného objektu. | Vytvorí nový objekt a rekurzívne skopíruje všetky polia vrátane vnorených objektov. |
| Zmeny na objekte | Odkazuje na vnorené objekty z pôvodného objektu, takže zmeny vnorených objektov ovplyvnia originál aj kópiu. | Úplne skopíruje vnorené objekty, takže zmeny v jednom objekte neovplyvnia druhý. |
| Pamäťová náročnosť | Pamäťovo efektívnejšie, pretože zdieľa odkazy na existujúce objekty. | Spotrebuje viac pamäte, pretože duplikuje všetky objekty a ich vnorené objekty. |
| Rýchlosť | Rýchlejšie, pretože kopíruje iba referencie. | Pomalšie, pretože potrebuje rekurzívne kopírovať všetky vnorené štruktúry. |
Klonovanie objektov v Jave nie je nič zložitého a výber medzi shallow copy a deep copy závisí od konkrétnych potrieb aplikácie. Shallow copy je rýchlejšia a menej náročná na zdroje, ale zdieľanie referencií môže viesť k nečakaným chybám. Naopak, deep copy zabezpečí úplnú nezávislosť kópií, avšak za cenu vyšších nárokov na výkon a pamäť. Je dôležité zohľadniť požiadavky na správanie objektov a podľa toho zvoliť vhodný prístup.
Ak ťa zaujíma pokročilejšie kopírovanie objektov, prečítaj si aj o návrhovom vzore Prototype, ktorý využíva klonovanie na vytváranie nových objektov.
Súvisiace články