Ako vytvoriť hlbokú kópiu objektu v Jave

1. Úvod

Keď chceme kopírovať objekt v Jave, musíme zvážiť dve možnosti - plytkú a hlbokú kópiu.

Plytká kópia predstavuje prístup, keď kopírujeme iba hodnoty poľa, a preto môže byť kópia závislá od pôvodného objektu. V prístupe k hlbokému kopírovaniu sa ubezpečujeme, že všetky objekty v strome sú hlboko kopírované, takže kopírovanie nezávisí od žiadneho predchádzajúceho existujúceho objektu, ktorý by sa mohol kedykoľvek zmeniť.

V tomto článku porovnáme tieto dva prístupy a naučíme sa štyri metódy implementácie hĺbkovej kópie.

2. Nastavenie Maven

Na testovanie rôznych spôsobov vykonania hĺbkovej kópie použijeme tri závislosti Maven - Gson, Jackson a Apache Commons Lang.

Pridajme tieto závislosti do našej pom.xml:

 com.google.code.gson gson 2.8.2 commons-lang commons-lang 2.6 com.fasterxml.jackson.core jackson-databind 2.9.3 

Najnovšie verzie Gson, Jackson a Apache Commons Lang nájdete na serveri Maven Central.

3. Model

Na porovnanie rôznych metód kopírovania objektov Java budeme potrebovať dve triedy, na ktorých budeme pracovať:

trieda Adresa {private String street; súkromné ​​mesto String; súkromná reťazcová krajina; // štandardné konštruktory, getre a setre}
trieda Používateľ {private String firstName; private String priezvisko; adresa súkromnej adresy; // štandardné konštruktory, getre a setre}

4. Plytká kópia

Plytká kópia je taká, v ktorej kopírujeme iba hodnoty polí z jedného objektu do druhého:

@Test public void whenShallowCopying_thenObjectsShouldNotBeSame () {Address address = new Address ("Downing St 10", "London", "England"); Používateľ pm = nový používateľ („predseda“, „minister“, adresa); Užívateľ shallowCopy = nový Používateľ (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); assertThat (shallowCopy) .isNotSameAs (pm); }

V tomto prípade pm! = plytké kopírovanie, čo znamená, že sú to rôzne objekty, ale problém je, že keď zmeníme ktorýkoľvek z pôvodných adresa ' vlastnosti, ovplyvní to tiež plytká kópiaAdresa.

Nerobili by sme si starosti, keby Adresa bol nemenný, ale nie je to:

@Test public void whenModifyingOriginalObject_ThenCopyShouldChange () {Address address = new Address ("Downing St 10", "London", "England"); Používateľ pm = nový používateľ („predseda“, „minister“, adresa); Užívateľ shallowCopy = nový Používateľ (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); address.setCountry ("Veľká Británia"); assertThat (shallowCopy.getAddress (). getCountry ()) .isEqualTo (pm.getAddress (). getCountry ()); }

5. Hlboká kópia

Hlboká kópia je alternatívou, ktorá rieši tento problém. Jeho výhodou je, že minimálne každý premenlivý objekt v grafe objektov sa rekurzívne kopíruje.

Pretože kópia nezávisí od žiadneho premenlivého objektu, ktorý bol vytvorený skôr, nebude upravená náhodou, ako sme videli pri plytkej kópii.

V nasledujúcich častiach si ukážeme niekoľko implementácií hlbokých kópií a ukážeme túto výhodu.

5.1. Kopírovať konštruktor

Prvá implementácia, ktorú implementujeme, je založená na kopírovacích konštruktoroch:

verejná adresa (adresa, ktorá) {this (that.getStreet (), that.getCity (), that.getCountry ()); }
public User (User that) {this (that.getFirstName (), that.getLastName (), new Address (that.getAddress ())); }

Vo vyššie uvedenej implementácii hlbokej kópie sme nevytvorili nové Struny v našom konštruktore kópií, pretože String je nemenná trieda.

Vďaka tomu sa nedajú náhodou upraviť. Pozrime sa, či to funguje:

@Test public void whenModifyingOriginalObject_thenCopyShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Používateľ pm = nový používateľ („predseda“, „minister“, adresa); Používateľ deepCopy = nový používateľ (pm); address.setCountry ("Veľká Británia"); assertNotEquals (pm.getAddress (). getCountry (), deepCopy.getAddress (). getCountry ()); }

5.2. Klonovateľné rozhranie

Druhá implementácia je založená na klonovej metóde zdedenej z Objekt. Je chránený, ale musíme ho prepísať ako verejné.

Pridáme tiež rozhranie značiek, Cloneable, do tried, aby naznačili, že triedy sú skutočne klonovateľné.

Pridajme klon () metóda do Adresa trieda:

@Override public Object clone () {try {return (Address) super.clone (); } catch (CloneNotSupportedException e) {vrátiť novú adresu (this.street, this.getCity (), this.getCountry ()); }}

A teraz poďme implementovať klon () pre Používateľ trieda:

@ Override public Object clone () {User user = null; try {user = (User) super.clone (); } catch (CloneNotSupportedException e) {user = new User (this.getFirstName (), this.getLastName (), this.getAddress ()); } user.address = (Adresa) this.address.clone (); návratový užívateľ; }

Všimnite si, že super.clone () call vráti plytkú kópiu objektu, ale hlboké kópie premenlivých polí sme nastavili ručne, takže výsledok je správny:

@Test public void whenModifyingOriginalObject_thenCloneCopyShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Používateľ pm = nový používateľ („predseda“, „minister“, adresa); Používateľ deepCopy = (Používateľ) pm.clone (); address.setCountry ("Veľká Británia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6. Externé knižnice

Vyššie uvedené príklady vyzerajú ľahko, ale niekedy neplatia ako riešenie keď nemôžeme pridať ďalší konštruktor alebo prepísať metódu klonovania.

To sa môže stať, keď nevlastníme kód, alebo keď je objektový graf taký komplikovaný, že by sme náš projekt nedokončili včas, ak by sme sa zamerali na písanie ďalších konštruktorov alebo implementáciu klon metóda na všetkých triedach v grafe objektov.

Čo potom? V takom prípade môžeme použiť externú knižnicu. Ak chcete dosiahnuť hlbokú kópiu, môžeme objekt serializovať a potom ho deserializovať na nový objekt.

Pozrime sa na niekoľko príkladov.

6.1. Apache Commons Lang

Apache Commons Lang má SerializationUtils # klon, ktorá vykonáva hĺbkovú kópiu, keď všetky triedy v grafe objektov implementujú Serializovateľné rozhranie.

Ak metóda narazí na triedu, ktorá nie je serializovateľná, zlyhá a vyhodí sa nezaškrtnutá SerializationException.

Z tohto dôvodu musíme pridať Serializovateľné rozhranie k našim triedam:

@Test public void whenModifyingOriginalObject_thenCommonsCloneShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Používateľ pm = nový používateľ („predseda“, „minister“, adresa); Používateľ deepCopy = (Používateľ) SerializationUtils.clone (pm); address.setCountry ("Veľká Británia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.2. Serializácia JSON s Gson

Ďalším spôsobom, ako serializovať, je použiť serializáciu JSON. Gson je knižnica, ktorá sa používa na prevod objektov do formátu JSON a naopak.

Na rozdiel od Apache Commons Lang, GSON nepotrebuje Serializovateľné rozhranie na uskutočnenie konverzií.

Poďme sa rýchlo pozrieť na príklad:

@Test public void whenModifyingOriginalObject_thenGsonCloneShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Používateľ pm = nový používateľ („predseda“, „minister“, adresa); Gson gson = nový Gson (); Používateľ deepCopy = gson.fromJson (gson.toJson (pm), User.class); address.setCountry ("Veľká Británia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.3. Serializácia JSON s Jacksonom

Jackson je ďalšia knižnica, ktorá podporuje serializáciu JSON. Táto implementácia bude veľmi podobná implementácii používajúcej Gson, ale musíme do našich tried pridať predvolený konštruktor.

Pozrime sa na príklad:

@ Test public void whenModifyingOriginalObject_thenJacksonCopyShouldNotChange () vyvolá IOException {Address address = new Address ("Downing St 10", "London", "England"); Používateľ pm = nový používateľ („predseda“, „minister“, adresa); ObjectMapper objectMapper = nový ObjectMapper (); Užívateľ deepCopy = objectMapper .readValue (objectMapper.writeValueAsString (pm), User.class); address.setCountry ("Veľká Británia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

7. Záver

Ktorú implementáciu by sme mali použiť pri vytváraní hlbokej kópie? Konečné rozhodnutie bude často závisieť od tried, ktoré skopírujeme, a od toho, či triedy vlastníme v grafe objektov.

Kompletné ukážky kódu pre tento tutoriál nájdete ako vždy na GitHub.


$config[zx-auto] not found$config[zx-overlay] not found