1. Introduktion
När vi vill kopiera ett objekt i Java finns det två möjligheter som vi måste tänka på - en grund kopia och en djup kopia.
Den grunda kopian är tillvägagångssättet när vi bara kopierar fältvärden och därför kan kopian vara beroende av det ursprungliga objektet. I djupkopieringsmetoden ser vi till att alla objekt i trädet kopieras djupt, så kopian är inte beroende av något tidigare existerande objekt som någonsin kan förändras.
I den här artikeln jämför vi dessa två tillvägagångssätt och lär oss fyra metoder för att implementera den djupa kopian.
2. Inställning av Maven
Vi använder tre Maven-beroenden - Gson, Jackson och Apache Commons Lang - för att testa olika sätt att utföra en djup kopia.
Låt oss lägga till dessa beroenden i vår 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 senaste versionerna av Gson, Jackson och Apache Commons Lang finns på Maven Central.
3. Modell
För att jämföra olika metoder för att kopiera Java-objekt behöver vi två klasser att arbeta med:
class Address { private String street; private String city; private String country; // standard constructors, getters and setters }
class User { private String firstName; private String lastName; private Address address; // standard constructors, getters and setters }
4. Grunt kopia
En grunt kopia är en där vi bara kopierar värden för fält från ett objekt till ett annat:
@Test public void whenShallowCopying_thenObjectsShouldNotBeSame() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User shallowCopy = new User( pm.getFirstName(), pm.getLastName(), pm.getAddress()); assertThat(shallowCopy) .isNotSameAs(pm); }
I det här fallet pm! = Ytlig , vilket innebär att de är olika objekt, men problemet är att när vi ändrar något av de ursprungliga adress egenskaper, kommer detta också att påverka ytlig : s adress .
Vi skulle inte bry oss om det om Adress var oföränderlig, men det är inte:
@Test public void whenModifyingOriginalObject_ThenCopyShouldChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User shallowCopy = new User( pm.getFirstName(), pm.getLastName(), pm.getAddress()); address.setCountry("Great Britain"); assertThat(shallowCopy.getAddress().getCountry()) .isEqualTo(pm.getAddress().getCountry()); }
5. Djup kopia
En djup kopia är ett alternativ som löser detta problem. Dess fördel är att åtminstone varje föränderligt objekt i objektgrafen kopieras rekursivt .
Eftersom kopian inte är beroende av något föränderligt objekt som skapades tidigare kommer det inte att ändras av misstag som vi såg med den grunda kopian.
I följande avsnitt visar vi flera implementeringar av djupa kopior och visar denna fördel.
5.1. Copy Constructor
Den första implementeringen vi implementerar är baserad på kopiekonstruktörer:
public Address(Address that) { this(that.getStreet(), that.getCity(), that.getCountry()); }
public User(User that) { this(that.getFirstName(), that.getLastName(), new Address(that.getAddress())); }
I ovanstående implementering av den djupa kopian har vi inte skapat nya strängar i vår kopiekonstruktör eftersom String är en oföränderlig klass.
Som ett resultat kan de inte ändras av misstag. Låt oss se om detta fungerar:
@Test public void whenModifyingOriginalObject_thenCopyShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User deepCopy = new User(pm); address.setCountry("Great Britain"); assertNotEquals( pm.getAddress().getCountry(), deepCopy.getAddress().getCountry()); }
5.2. Klonabelt gränssnitt
Den andra implementeringen baseras på den klonmetod som ärvs från Object . Det är skyddat, men vi måste åsidosätta det som offentligt .
Vi lägger också till ett markörgränssnitt, Cloneable, i klasserna för att indikera att klasserna faktiskt är klonbara.
Låt oss lägga till klonmetoden () i adressklassen :
@Override public Object clone() { try { return (Address) super.clone(); } catch (CloneNotSupportedException e) { return new Address(this.street, this.getCity(), this.getCountry()); } }
Och nu ska vi genomföra clone () för Användarklass:
@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 = (Address) this.address.clone(); return user; }
Observera att super.clone () -anropet returnerar en grunt kopia av ett objekt, men vi ställer in djupa kopior av mutabla fält manuellt så att resultatet blir korrekt:
@Test public void whenModifyingOriginalObject_thenCloneCopyShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User deepCopy = (User) pm.clone(); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }
6. Externa bibliotek
Ovanstående exempel ser enkla ut, men ibland gäller de inte som en lösning när vi inte kan lägga till ytterligare en konstruktör eller åsidosätta klonmetoden .
Detta kan hända när vi inte äger koden, eller när objektgrafen är så komplicerad att vi inte skulle avsluta vårt projekt i tid om vi fokuserade på att skriva ytterligare konstruktörer eller implementera klonmetoden på alla klasser i objektgrafen.
Vad händer då? I det här fallet kan vi använda ett externt bibliotek. För att uppnå en djup kopia kan vi serieera ett objekt och deserialisera det till ett nytt objekt .
Låt oss titta på några exempel.
6.1. Apache Commons Lang
Apache Commons Lang has SerializationUtils#clone, which performs a deep copy when all classes in the object graph implement the Serializable interface.
If the method encounters a class that isn't serializable, it'll fail and throw an unchecked SerializationException.
Because of that, we need to add the Serializable interface to our classes:
@Test public void whenModifyingOriginalObject_thenCommonsCloneShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User deepCopy = (User) SerializationUtils.clone(pm); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }
6.2. JSON Serialization With Gson
The other way to serialize is to use JSON serialization. Gson is a library that's used for converting objects into JSON and vice versa.
Unlike Apache Commons Lang, GSON does not need the Serializable interface to make the conversions.
Let's have a quick look at an example:
@Test public void whenModifyingOriginalObject_thenGsonCloneShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); Gson gson = new Gson(); User deepCopy = gson.fromJson(gson.toJson(pm), User.class); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }
6.3. JSON Serialization With Jackson
Jackson är ett annat bibliotek som stöder JSON-serialisering. Denna implementering kommer att likna den som använder Gson, men vi måste lägga till standardkonstruktören i våra klasser .
Låt oss se ett exempel:
@Test public void whenModifyingOriginalObject_thenJacksonCopyShouldNotChange() throws IOException { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); ObjectMapper objectMapper = new ObjectMapper(); User deepCopy = objectMapper .readValue(objectMapper.writeValueAsString(pm), User.class); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }
7. Slutsats
Vilket implementering ska vi använda när vi gör en djup kopia? Det slutgiltiga beslutet beror ofta på de klasser vi kopierar och om vi äger klasserna i objektgrafen.
Som alltid kan de fullständiga kodproverna för den här självstudien hittas på GitHub.