How do I make a deep or selective copy of an object?
← Classes and OOP · Ref: Q2100
THE COPY PRINCIPLE
- 'default operator :=:' is a SHALLOW copy: after 'target :=: source' each of target's properties refers to the SAME object as source's - exactly what 'b <- a' and passing a parameter do. Change a shared object in place ('points += 2') and both see it.
- A built-in collection copies at its OWN level: 'list :=: other' gives list its own container holding the same element objects. A value type copies its value: 'year :=: other.year'. Integer(x), String(x) and your own copy constructors make new objects.
- Anything deeper or SELECTIVE is your code: write 'operator :=:' yourself, or a domain method, and decide per property - duplicate it, share it, or leave it out. No generator can know which, so EK9 does not guess. There is no clone().
The program below renews a car insurance policy for the next period. The new period's policy:
- DUPLICATES the per-holder data: each named driver is a new Driver (Driver(driver)), so the new period can add a driver or penalty points without touching last period's record;
- SHARES the insurer's standard wordings: the same Wording objects, in the new policy's own List ('wordings :=: other.wordings'), so a correction to a wording reaches every policy that uses it;
- DROPS the endorsements that have expired.
The copying rules live in a hand-written 'operator :=:'; the renewal rules (next period, expired endorsements) live in 'renewForNextPeriod()'. The original period is printed after the renewal is changed: it is untouched, apart from the wording it shares on purpose.
Example
defines module qa.classes.deeporselectivecopy defines class <?- Per-holder data: each period has its own -?> Driver name as String: String() points as Integer: Integer() default private Driver() Driver() -> driverName as String driverPoints as Integer name :=: driverName points :=: driverPoints <?- A copy constructor: new objects for every property -?> Driver() -> other as Driver name :=: other.name points :=: other.points named() as pure -> driverName as String <- rtn as Boolean: name == driverName addPoints() -> morePoints as Integer points += morePoints operator $ as pure <- rtn as String: `${name}(${points})` default operator ? <?- The insurer's standard text: shared by every policy that uses it -?> Wording text as String: String() default private Wording() Wording() -> wordingText as String text :=: wordingText revise() -> revisedText as String text :=: revisedText operator $ as pure <- rtn as String: text default operator ? Endorsement text as String: String() lastPeriod as Integer: Integer() default private Endorsement() Endorsement() -> endorsementText as String endorsementLastPeriod as Integer text :=: endorsementText lastPeriod :=: endorsementLastPeriod coversPeriod() as pure -> periodToCheck as Integer <- rtn as Boolean: periodToCheck <= lastPeriod operator $ as pure <- rtn as String: `${text} to ${lastPeriod}` default operator ? Policy holder as String: String() period as Integer: Integer() drivers as List of Driver: List() of Driver wordings as List of Wording: List() of Wording endorsements as List of Endorsement: List() of Endorsement default private Policy() Policy() -> policyHolder as String policyPeriod as Integer holder :=: policyHolder period :=: policyPeriod addDriver() -> driver as Driver drivers += driver addWording() -> wording as Wording wordings += wording addEndorsement() -> endorsement as Endorsement endorsements += endorsement addPointsTo() -> driverName as String morePoints as Integer for driver in drivers if driver.named(driverName) driver.addPoints(morePoints) <?- The next period: a copy of this policy, moved on a period, without what has expired -?> renewForNextPeriod() <- renewed as Policy: Policy() renewed :=: this renewed.startNextPeriod() private startNextPeriod() period++ stillInForce <- List() of Endorsement for endorsement in endorsements if endorsement.coversPeriod(period) stillInForce += endorsement endorsements: stillInForce describe() as pure <- rtn as String: `${period} ${holder} drivers=${drivers} wordings=${wordings} endorsements=${endorsements}` <?- A selective copy: duplicate the per-holder data, share the standard wordings -?> operator :=: -> other as Policy holder :=: other.holder period :=: other.period drivers: List() of Driver for driver in other.drivers drivers += Driver(driver) wordings :=: other.wordings endorsements :=: other.endorsements default operator ? defines program RenewPolicy() stdout <- Stdout() standardTerms <- Wording("Standard terms v1") current <- Policy("Pat", 2026) current.addDriver(Driver("Alex", 3)) current.addWording(standardTerms) current.addEndorsement(Endorsement("Courtesy car", 2026)) current.addEndorsement(Endorsement("Protected bonus", 2028)) renewal <- current.renewForNextPeriod() renewal.addDriver(Driver("Sam", 0)) renewal.addPointsTo("Alex", 2) stdout.println(current.describe()) stdout.println(renewal.describe()) standardTerms.revise("Standard terms v2") stdout.println(current.describe()) stdout.println(renewal.describe())
Common mistakes
E50060 — EK9 has no clone(): no generated method can know which properties to duplicate, share or drop. Write the copy yourself - an 'operator :=:' or a domain method such as renewForNextPeriod(). See ek9 -h E50060 for details.
Incorrect:
renewal <- current.clone()
Correct:
renewal <- current.renewForNextPeriod()
Other ways to ask this
- Is 'default operator :=:' a deep copy or a shallow copy?
- How do I copy an object so that changing the copy does not change the original?
- How do I clone an object in EK9?
- How do I copy some fields, share others and leave some out?
Coming from another language?
Java: clone() is shallow and Cloneable is widely considered broken; deep copies are hand-written copy constructors or serialisation tricks. C#: MemberwiseClone() is shallow. Python: copy.copy() is shallow, copy.deepcopy() copies everything reachable (it cannot share or drop selectively). C++: the implicit copy constructor copies members, so a pointer member is shared. EK9: 'default operator :=:' is shallow, a collection copies at its own level, and a deep or selective copy is an 'operator :=:' or domain method you write.
Keywords: copy constructor, renewal, selective copy, copy, shared reference, shallow copy, deep copy, operator :=:, aliasing, clone, :=:, default operator