Sådan laver du en dyb kopi af et objekt i Java

1. Introduktion

Når vi vil kopiere et objekt i Java, er der to muligheder, som vi skal overveje - en lav kopi og en dyb kopi.

Den lave kopi er fremgangsmåden, når vi kun kopierer feltværdier, og kopien kan derfor være afhængig af det originale objekt. I dybkopieringsmetoden sørger vi for, at alle objekterne i træet kopieres dybt, så kopien ikke er afhængig af noget tidligere eksisterende objekt, der nogensinde kan ændre sig.

I denne artikel sammenligner vi disse to tilgange og lærer fire metoder til implementering af den dybe kopi.

2. Maven-opsætning

Vi bruger tre Maven-afhængigheder - Gson, Jackson og Apache Commons Lang - til at teste forskellige måder at udføre en dyb kopi på.

Lad os tilføje disse afhængigheder til vores 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 

De nyeste versioner af Gson, Jackson og Apache Commons Lang findes på Maven Central.

3. Model

For at sammenligne forskellige metoder til kopiering af Java-objekter skal vi bruge to klasser til at arbejde på:

klasse Adresse {privat String street; private String by; privat strengland; // standardkonstruktører, getters og settere}
klasse bruger {privat streng fornavn; privat streng efternavn; privat adresse adresse // standardkonstruktører, getters og settere}

4. Lav kopi

En lav kopi er en, i hvilken vi kopierer kun værdier for felter fra et objekt til et andet:

@Test offentligt ugyldigt nårShallowCopying_thenObjectsShouldNotBeSame () {Address address = new Address ("Downing St 10", "London", "England"); Bruger pm = ny bruger ("Prime", "Minister", adresse); Bruger shallowCopy = ny bruger (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); assertThat (shallowCopy) .isNotSameAs (pm); }

I dette tilfælde pm! = lav kopi, hvilket betyder at de er forskellige objekter, men problemet er, at når vi ændrer noget af originalen adresse' egenskaber, dette vil også påvirke overfladisk kopi'S adresse.

Vi ville ikke bekymre os om det, hvis Adresse var uforanderlig, men det er ikke:

@Test offentlig ugyldig nårModifyingOriginalObject_ThenCopyShouldChange () {Adresse adresse = ny adresse ("Downing St 10", "London", "England"); Bruger pm = ny bruger ("Prime", "Minister", adresse); Bruger shallowCopy = ny bruger (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); address.setCountry ("Storbritannien"); assertThat (shallowCopy.getAddress (). getCountry ()) .isEqualTo (pm.getAddress (). getCountry ()); }

5. Dyb kopi

En dyb kopi er et alternativ, der løser dette problem. Dens fordel er, at i det mindste hvert ændrede objekt i objektgrafen kopieres rekursivt.

Da kopien ikke er afhængig af noget ændret objekt, der blev oprettet tidligere, bliver den ikke ændret ved et uheld som vi så med den lave kopi.

I de følgende afsnit viser vi flere implementeringer af dybe kopier og demonstrerer denne fordel.

5.1. Copy Constructor

Den første implementering, vi implementerer, er baseret på kopikonstruktører:

public address (Address that) {denne (that.getStreet (), that.getCity (), that.getCountry ()); }
offentlig bruger (bruger, der) {denne (that.getFirstName (), that.getLastName (), ny adresse (that.getAddress ())); }

I ovenstående implementering af den dybe kopi har vi ikke oprettet nye Strenge i vores kopi konstruktør fordi Snor er en uforanderlig klasse.

Som et resultat kan de ikke ændres ved et uheld. Lad os se om dette fungerer:

@Test offentlig ugyldig nårModifyingOriginalObject_thenCopyShouldNotChange () {Adresse adresse = ny adresse ("Downing St 10", "London", "England"); Bruger pm = ny bruger ("Prime", "Minister", adresse); Bruger deepCopy = ny bruger (pm); address.setCountry ("Storbritannien"); assertNotEquals (pm.getAddress (). getCountry (), deepCopy.getAddress (). getCountry ()); }

5.2. Klonabelt interface

Den anden implementering er baseret på den klonmetode, der er nedarvet fra Objekt. Det er beskyttet, men vi er nødt til at tilsidesætte det som offentlig.

Vi tilføjer også en markørgrænseflade, Klonbar, til klasserne for at indikere, at klasserne faktisk er klonbare.

Lad os tilføje klon () metode til Adresse klasse:

@ Override public Object clone () {prøv {return (Address) super.clone (); } fange (CloneNotSupportedException e) {returner ny adresse (this.street, this.getCity (), this.getCountry ()); }}

Og lad os nu gennemføre klon () til Bruger klasse:

@ Override public Object clone () {Brugerbruger = null; prøv {user = (User) super.clone (); } fange (CloneNotSupportedException e) {user = new User (this.getFirstName (), this.getLastName (), this.getAddress ()); } user.address = (Adresse) this.address.clone (); tilbagevendende bruger }

Bemærk, at super.clone () call returnerer en lav kopi af et objekt, men vi indstiller dybe kopier af mutable felter manuelt, så resultatet er korrekt:

@Test offentlig ugyldig nårModifyingOriginalObject_thenCloneCopyShouldNotChange () {Adresse adresse = ny adresse ("Downing St 10", "London", "England"); Bruger pm = ny bruger ("Prime", "Minister", adresse); Bruger deepCopy = (Bruger) pm.clone (); address.setCountry ("Storbritannien"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6. Eksterne biblioteker

Ovenstående eksempler ser lette ud, men nogle gange gælder de ikke som en løsning når vi ikke kan tilføje en ekstra konstruktør eller tilsidesætte klonmetoden.

Dette kan ske, når vi ikke ejer koden, eller når objektgrafen er så kompliceret, at vi ikke ville afslutte vores projekt til tiden, hvis vi fokuserede på at skrive yderligere konstruktører eller implementere klon metode på alle klasser i objektgrafen.

Hvad så? I dette tilfælde kan vi bruge et eksternt bibliotek. For at opnå en dyb kopi vi kan serieisere et objekt og derefter deserialisere det til et nyt objekt.

Lad os se på et par eksempler.

6.1. Apache Commons Lang

Apache Commons Lang har SerializationUtils # klon, der udfører en dyb kopi, når alle klasser i objektgrafen implementerer Serialiserbar interface.

Hvis metoden støder på en klasse, der ikke kan serialiseres, mislykkes den og kaster et ukontrolleret SerializationException.

På grund af det er vi nødt til at tilføje Serialiserbar interface til vores klasser:

@Test offentlig ugyldig nårModifyingOriginalObject_thenCommonsCloneShouldNotChange () {Adresse adresse = ny adresse ("Downing St 10", "London", "England"); Bruger pm = ny bruger ("Prime", "Minister", adresse); User deepCopy = (User) SerializationUtils.clone (pm); address.setCountry ("Storbritannien"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.2. JSON Serialization With Gson

Den anden måde at serialisere er at bruge JSON-serialisering. Gson er et bibliotek, der bruges til at konvertere objekter til JSON og omvendt.

I modsætning til Apache Commons Lang, GSON har ikke brug for Serialiserbar interface til at foretage konverteringer.

Lad os se hurtigt på et eksempel:

@Test offentlig ugyldig nårModifyingOriginalObject_thenGsonCloneShouldNotChange () {Adresse-adresse = ny adresse ("Downing St 10", "London", "England"); Bruger pm = ny bruger ("Prime", "Minister", adresse); Gson gson = ny Gson (); Bruger deepCopy = gson.fromJson (gson.toJson (pm), User.class); address.setCountry ("Storbritannien"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.3. JSON Serialization With Jackson

Jackson er et andet bibliotek, der understøtter JSON-serialisering. Denne implementering vil være meget lig den, der bruger Gson, men vi er nødt til at tilføje standardkonstruktøren til vores klasser.

Lad os se et eksempel:

@Test offentlig ugyldig nårModifyingOriginalObject_thenJacksonCopyShouldNotChange () kaster IOException {Adresse adresse = ny adresse ("Downing St 10", "London", "England"); Bruger pm = ny bruger ("Prime", "Minister", adresse); ObjectMapper objectMapper = ny ObjectMapper (); Bruger deepCopy = objectMapper .readValue (objectMapper.writeValueAsString (pm), User.class); address.setCountry ("Storbritannien"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

7. Konklusion

Hvilken implementering skal vi bruge, når vi laver en dyb kopi? Den endelige beslutning afhænger ofte af de klasser, vi kopierer, og om vi ejer klasserne i objektgrafen.

Som altid kan de komplette kodeeksempler til denne tutorial findes på GitHub.


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