Hoe maak je een diepe kopie van een object in Java

1. Inleiding

Als we een object in Java willen kopiëren, zijn er twee mogelijkheden die we in overweging moeten nemen: een ondiepe kopie en een diepe kopie.

De ondiepe kopie is de benadering wanneer we alleen veldwaarden kopiëren en daarom kan de kopie afhankelijk zijn van het originele object. Bij de deep copy-benadering zorgen we ervoor dat alle objecten in de boomstructuur diep worden gekopieerd, zodat de kopie niet afhankelijk is van een eerder bestaand object dat ooit zou kunnen veranderen.

In dit artikel zullen we deze twee benaderingen vergelijken en vier methoden leren om de diepe kopie te implementeren.

2. Maven-instellingen

We zullen drie Maven-afhankelijkheden gebruiken - Gson, Jackson en Apache Commons Lang - om verschillende manieren te testen om een ​​diepe kopie uit te voeren.

Laten we deze afhankelijkheden toevoegen aan onze 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 nieuwste versies van Gson, Jackson en Apache Commons Lang zijn te vinden op Maven Central.

3. Model

Om verschillende methoden te vergelijken om Java-objecten te kopiëren, hebben we twee klassen nodig om aan te werken:

klasse Adres {privé String street; particuliere String stad; privé String land; // standaard constructeurs, getters en setters}
klasse Gebruiker {private String firstName; private String achternaam; privé adres adres; // standaard constructeurs, getters en setters}

4. Ondiepe kopie

Een ondiepe kopie is er een waarin we kopiëren alleen waarden van velden van het ene object naar het andere:

@Test openbare leegte whenShallowCopying_thenObjectsShouldNotBeSame () {Adresadres = nieuw adres ("Downing St 10", "Londen", "Engeland"); Gebruiker pm = nieuwe gebruiker ("Prime", "Minister", adres); Gebruiker shallowCopy = nieuwe gebruiker (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); assertThat (shallowCopy) .isNotSameAs (pm); }

In dit geval pm! = shallowCopy, wat betekent dat het zijn verschillende objecten, maar het probleem is dat wanneer we iets van het origineel veranderen adres' eigenschappen, dit heeft ook invloed op de Oppervlakkige kopie‘S adres.

We zouden er niet om doen als Adres was onveranderlijk, maar het is niet:

@Test openbaar ongeldig whenModifyingOriginalObject_ThenCopyShouldChange () {Adresadres = nieuw adres ("Downing St 10", "Londen", "Engeland"); Gebruiker pm = nieuwe gebruiker ("Prime", "Minister", adres); Gebruiker shallowCopy = nieuwe gebruiker (pm.getFirstName (), pm.getLastName (), pm.getAddress ()); address.setCountry ("Groot-Brittannië"); assertThat (shallowCopy.getAddress (). getCountry ()) .isEqualTo (pm.getAddress (). getCountry ()); }

5. Diepe kopie

Een diepe kopie is een alternatief dat dit probleem oplost. Het voordeel is dat tenminste elk veranderlijk object in de objectgrafiek wordt recursief gekopieerd.

Omdat de kopie niet afhankelijk is van een veranderlijk object dat eerder is gemaakt, wordt deze niet per ongeluk gewijzigd, zoals we zagen met de ondiepe kopie.

In de volgende secties laten we verschillende implementaties van diepe kopieën zien en demonstreren we dit voordeel.

5.1. Kopieer Constructor

De eerste implementatie die we zullen implementeren is gebaseerd op copy constructors:

openbaar adres (adres dat) {this (that.getStreet (), that.getCity (), that.getCountry ()); }
openbare gebruiker (gebruiker dat) {this (that.getFirstName (), that.getLastName (), nieuw adres (that.getAddress ())); }

In de bovenstaande implementatie van de diepe kopie hebben we geen nieuwe gemaakt Snaren in onze kopieerconstructor omdat Draad is een onveranderlijke klasse.

Als gevolg hiervan kunnen ze niet per ongeluk worden gewijzigd. Eens kijken of dit werkt:

@Test public void whenModifyingOriginalObject_thenCopyShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Gebruiker pm = nieuwe gebruiker ("Prime", "Minister", adres); Gebruiker deepCopy = nieuwe gebruiker (pm); address.setCountry ("Groot-Brittannië"); assertNotEquals (pm.getAddress (). getCountry (), deepCopy.getAddress (). getCountry ()); }

5.2. Cloneable interface

De tweede implementatie is gebaseerd op de kloonmethode die is geërfd van Voorwerp. Het is beschermd, maar we moeten het opheffen als openbaar.

We zullen ook een markeringsinterface toevoegen, Te klonen, naar de klassen om aan te geven dat de klassen daadwerkelijk kunnen worden gekloond.

Laten we de kloon () methode naar de Adres klasse:

@Override public Object clone () {probeer {return (Address) super.clone (); } catch (CloneNotSupportedException e) {retourneer nieuw adres (this.street, this.getCity (), this.getCountry ()); }}

En laten we nu implementeren kloon () voor de Gebruiker klasse:

@Override public Object clone () {User user = null; probeer {user = (User) super.clone (); } catch (CloneNotSupportedException e) {gebruiker = nieuwe gebruiker (this.getFirstName (), this.getLastName (), this.getAddress ()); } user.address = (Adres) this.address.clone (); terugkeer gebruiker; }

Merk op dat de super.clone () aanroep retourneert een ondiepe kopie van een object, maar we hebben handmatig diepe kopieën van veranderlijke velden ingesteld, dus het resultaat is correct:

@Test public void whenModifyingOriginalObject_thenCloneCopyShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Gebruiker pm = nieuwe gebruiker ("Prime", "Minister", adres); Gebruiker deepCopy = (Gebruiker) pm.clone (); address.setCountry ("Groot-Brittannië"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6. Externe bibliotheken

De bovenstaande voorbeelden zien er eenvoudig uit, maar zijn soms niet van toepassing als oplossing wanneer we geen extra constructor kunnen toevoegen of de kloonmethode kunnen negeren.

Dit kan gebeuren als we niet de eigenaar zijn van de code, of als de objectgrafiek zo ingewikkeld is dat we ons project niet op tijd zouden afronden als we ons zouden concentreren op het schrijven van extra constructors of het implementeren van de kloon methode voor alle klassen in de objectgrafiek.

Wat dan? In dit geval kunnen we een externe bibliotheek gebruiken. Om een ​​diepe kopie te krijgen, we kunnen een object serialiseren en het vervolgens deserialiseren naar een nieuw object.

Laten we een paar voorbeelden bekijken.

6.1. Apache Commons Lang

Apache Commons Lang heeft SerializationUtils # kloon, die een diepe kopie uitvoert wanneer alle klassen in de objectgrafiek de Serialiseerbaar koppel.

Als de methode een klasse tegenkomt die niet serialiseerbaar is, zal deze mislukken en een ongecontroleerd SerializationException.

Daarom moeten we de Serialiseerbaar interface naar onze lessen:

@Test public void whenModifyingOriginalObject_thenCommonsCloneShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Gebruiker pm = nieuwe gebruiker ("Prime", "Minister", adres); Gebruiker deepCopy = (Gebruiker) SerializationUtils.clone (pm); address.setCountry ("Groot-Brittannië"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.2. JSON-serialisering met Gson

De andere manier om te serialiseren is door JSON-serialisering te gebruiken. Gson is een bibliotheek die wordt gebruikt voor het converteren van objecten naar JSON en vice versa.

In tegenstelling tot Apache Commons Lang, GSON heeft de Serialiseerbaar interface om de conversies te maken.

Laten we snel een voorbeeld bekijken:

@Test public void whenModifyingOriginalObject_thenGsonCloneShouldNotChange () {Address address = new Address ("Downing St 10", "London", "England"); Gebruiker pm = nieuwe gebruiker ("Prime", "Minister", adres); Gson gson = nieuwe Gson (); Gebruiker deepCopy = gson.fromJson (gson.toJson (pm), User.class); address.setCountry ("Groot-Brittannië"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

6.3. JSON-serialisering met Jackson

Jackson is een andere bibliotheek die JSON-serialisering ondersteunt. Deze implementatie lijkt erg op die met Gson, maar we moeten de standaardconstructor aan onze klassen toevoegen.

Laten we een voorbeeld bekijken:

@Test public void whenModifyingOriginalObject_thenJacksonCopyShouldNotChange () gooit IOException {Address address = new Address ("Downing St 10", "London", "England"); Gebruiker pm = nieuwe gebruiker ("Prime", "Minister", adres); ObjectMapper objectMapper = nieuwe ObjectMapper (); Gebruiker deepCopy = objectMapper .readValue (objectMapper.writeValueAsString (pm), User.class); address.setCountry ("Groot-Brittannië"); assertThat (deepCopy.getAddress (). getCountry ()) .isNotEqualTo (pm.getAddress (). getCountry ()); }

7. Conclusie

Welke implementatie moeten we gebruiken bij het maken van een diepe kopie? De uiteindelijke beslissing hangt vaak af van de klassen die we zullen kopiëren en of we eigenaar zijn van de klassen in de objectgrafiek.

Zoals altijd zijn de volledige codevoorbeelden voor deze tutorial te vinden op GitHub.


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