Hogyan készítsünk egy objektum mély másolatát Java-ban

1. Bemutatkozás

Amikor egy objektumot Java-ba akarunk másolni, két lehetőséget kell megvizsgálnunk - egy sekély és egy mély másolatot.

A sekély másolat az a megközelítés, amikor csak mezőértékeket másolunk, és ezért a másolat függhet az eredeti objektumtól. A mélymásolásos megközelítés során meggyőződünk arról, hogy a fa összes objektuma mélyen másolva van-e, így a másolat nem függ semmilyen korábbi létező objektumtól, amely valaha is megváltozhat.

Ebben a cikkben összehasonlítjuk ezt a két megközelítést, és négy módszert tanulunk meg a mély másolat megvalósításához.

2. Maven Setup

Három Maven-függőséget - Gson, Jackson és Apache Commons Lang - fogunk használni a mély másolat végrehajtásának különböző módjainak tesztelésére.

Adjuk hozzá ezeket a függőségeket a sajátunkhoz pom.xml:

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

A Gson, Jackson és az Apache Commons Lang legújabb verziói a Maven Central oldalon találhatók.

3. Modell

Különböző módszerek összehasonlításához a Java objektumok másolásához két osztályra lesz szükségünk:

osztály Cím {privát String utca; privát String város; privát String ország; // szabványos kivitelezők, mérőeszközök és beállítók}
osztály Felhasználó {private String keresztnév; privát karakterlánc vezetéknév; privát cím címe; // szabványos kivitelezők, mérőeszközök és beállítók}

4. Sekély másolat

A sekély példány az, amelyben csak a mezők értékeit másoljuk egyik tárgyról a másikra:

@Test public void whenShallowCopying_thenObjectsShouldNotBeSame () {Address address = new Address ("Downing St 10", "London", "England"); Pm felhasználó = új felhasználó ("miniszterelnök", "miniszter", cím); Felhasználó shallowCopy = új felhasználó (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); assertThat (shallowCopy) .isNotSameAs (pm); }

Ebben az esetben pm! = sekély másolat, ami azt jelenti különböző tárgyak, de a probléma az, hogy amikor az eredetit megváltoztatjuk cím' tulajdonságokkal, ez a sekélyCopyCíme.

Nem zavarnánk, ha Cím változhatatlan volt, de nem az:

@Test public void whenModifyingOriginalObject_ThenCopyShouldChange () {Address address = new Address ("Downing St 10", "London", "England"); Pm felhasználó = új felhasználó ("miniszterelnök", "miniszter", cím); Felhasználó shallowCopy = új felhasználó (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); address.setCountry ("Nagy-Britannia"); assertThat (shallowCopy.getAddress (). getCountry ()) .isEqualTo (pm.getAddress (). getCountry ()); }

5. Mély másolat

A mély másolat egy alternatíva, amely megoldja ezt a problémát. Előnye, hogy legalább az objektumdiagram minden egyes módosítható objektuma rekurzív módon másolásra kerül.

Mivel a másolat nem függ semmilyen korábban módosítható objektumtól, véletlenül nem módosul, mint ahogy azt a sekély példánynál láttuk.

A következő szakaszokban több mély másolás megvalósítását mutatjuk be, és bemutatjuk ezt az előnyt.

5.1. Copy Constructor

Az első megvalósítás, amelyet másolatok készítőin alapulnak:

public Address (Address that) {this (that.getStreet (), that.getCity (), that.getCountry ()); }
public User (Felhasználó that) {this (that.getFirstName (), that.getLastName (), new Address (that.getAddress ())); }

A mély másolat fenti megvalósításában nem hoztunk létre újat Húrok másolatkészítőnkben, mert Húr változhatatlan osztály.

Ennek eredményeként nem módosíthatók véletlenül. Lássuk, működik-e ez:

@Test public void whenModifyingOriginalObject_thenCopyShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Pm felhasználó = új felhasználó ("miniszterelnök", "miniszter", cím); Felhasználó deepCopy = új felhasználó (pm); address.setCountry ("Nagy-Britannia"); assertNotEquals (pm.getAddress (). getCountry (), deepCopy.getAddress (). getCountry ()); }

5.2. Klónozható felület

A második megvalósítás az átörökített klón módszeren alapszik Tárgy. Védett, de felül kell írnunk nyilvános.

Hozzáadunk egy marker felületet is, Klónozható, az osztályoknak, hogy jelezzék, hogy az osztályok valóban klónozhatók.

Tegyük hozzá a klón () módszer a Cím osztály:

@Orride public Object clone () {try {return (Address) super.clone (); } catch (CloneNotSupportedException e) {return new Address (this. utca, this.getCity (), this.getCountry ()); }}

És most hajtsuk végre klón () a Felhasználó osztály:

@Orride public Object clone () {Felhasználó felhasználó = null; próbáld ki a {user = (Felhasználó) super.clone (); } catch (CloneNotSupportedException e) {user = új felhasználó (this.getFirstName (), this.getLastName (), this.getAddress ()); } felhasználó.cím = (Cím) this.cím.klón (); visszatérő felhasználó; }

Vegye figyelembe, hogy a super.clone () A call egy objektum sekély másolatát adja vissza, de a módosítható mezők mély másolatait manuálisan állítjuk be, így az eredmény helyes:

@Test public void whenModifyingOriginalObject_thenCloneCopyShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Pm felhasználó = új felhasználó ("miniszterelnök", "miniszter", cím); Felhasználó deepCopy = (Felhasználó) pm.clone (); address.setCountry ("Nagy-Britannia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6. Külső könyvtárak

A fenti példák egyszerűnek tűnnek, de néha nem alkalmazhatók megoldásként amikor nem adhatunk hozzá további konstruktort vagy felülírhatjuk a klónozási módszert.

Ez akkor fordulhat elő, ha nem a kód birtokában vagyunk, vagy ha az objektumgrafikon olyan bonyolult, hogy nem fejeznénk be időben a projektünket, ha további konstruktorok írására vagy a klón módszer az objektumdiagram összes osztályán.

Akkor mit? Ebben az esetben használhatunk külső könyvtárat. A mély másolat elérése érdekében sorosíthatunk egy objektumot, majd deszerializálhatjuk egy új objektummá.

Nézzünk meg néhány példát.

6.1. Apache Commons Lang

Apache Commons Lang-nek van SerializationUtils # clone, amely mély másolatot hajt végre, amikor az objektumgráf összes osztálya megvalósítja a Sorosítható felület.

Ha a módszer olyan osztállyal találkozik, amely nem sorosítható, akkor meghiúsul, és ellenőrizetlenet dob SerializationException.

Emiatt hozzá kell adnunk a Sorosítható interfész osztályainkhoz:

@Test public void whenModifyingOriginalObject_thenCommonsCloneShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Pm felhasználó = új felhasználó ("miniszterelnök", "miniszter", cím); Felhasználó deepCopy = (Felhasználó) SerializationUtils.clone (pm); address.setCountry ("Nagy-Britannia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.2. JSON-sorozatosítás Gson-nal

A sorozatosítás másik módja a JSON-szerializáció használata. A Gson egy könyvtár, amely objektumokat alakít át JSON-vá és fordítva.

Az Apache Commons Lang-től eltérően A GSON-nak nincs szüksége a Sorosítható felület az átalakítások elvégzéséhez.

Nézzünk meg egy példát gyorsan:

@Test public void whenModifyingOriginalObject_thenGsonCloneShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Pm felhasználó = új felhasználó ("miniszterelnök", "miniszter", cím); Gson gson = új Gson (); Felhasználó deepCopy = gson.fromJson (gson.toJson (pm), User.class); address.setCountry ("Nagy-Britannia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.3. JSON szerializálás Jackson-szal

A Jackson egy másik könyvtár, amely támogatja a JSON sorosítást. Ez a megvalósítás nagyon hasonló lesz a Gson-t használóéval, de hozzá kell adnunk az alapértelmezett konstruktort az osztályainkhoz.

Lássunk egy példát:

@Test public void whenModifyingOriginalObject_thenJacksonCopyShouldNotChange () IOException {Address address = new Address ("Downing St 10", "London", "England") dobja; Pm felhasználó = új felhasználó ("miniszterelnök", "miniszter", cím); ObjectMapper objectMapper = új ObjectMapper (); Felhasználó deepCopy = objectMapper .readValue (objectMapper.writeValueAsString (pm), User.class); address.setCountry ("Nagy-Britannia"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

7. Következtetés

Melyik megvalósítást kell használnunk mély másolat készítésekor? A végső döntés gyakran attól függ, hogy mely osztályokat másoljuk át, és hogy az objektumok grafikonjában mi vagyunk-e az osztályok.

Mint mindig, az oktatóanyag teljes kódmintái megtalálhatók a GitHubon.